@resolveio/server-lib 22.3.221 → 22.3.222
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/ai/assistant-core-heuristics.d.ts +11 -0
- package/ai/assistant-core-heuristics.js +356 -0
- package/ai/assistant-core-heuristics.js.map +1 -0
- package/ai/resolveio-platform-intelligence-memory-corpus.d.ts +3 -0
- package/ai/resolveio-platform-intelligence-memory-corpus.js +214 -0
- package/ai/resolveio-platform-intelligence-memory-corpus.js.map +1 -0
- package/ai/resolveio-platform-intelligence-memory.d.ts +20 -0
- package/ai/resolveio-platform-intelligence-memory.js +341 -0
- package/ai/resolveio-platform-intelligence-memory.js.map +1 -0
- package/{src/ai/resolveio-platform-intelligence-types.ts → ai/resolveio-platform-intelligence-types.d.ts} +15 -20
- package/ai/resolveio-platform-intelligence-types.js +4 -0
- package/ai/resolveio-platform-intelligence-types.js.map +1 -0
- package/ai/resolveio-platform-intelligence.d.ts +6 -0
- package/ai/resolveio-platform-intelligence.js +463 -0
- package/ai/resolveio-platform-intelligence.js.map +1 -0
- package/client-server-app.d.ts +1 -0
- package/client-server-app.js +68 -0
- package/client-server-app.js.map +1 -0
- package/collections/ai-run.collection.d.ts +3 -0
- package/collections/ai-run.collection.js +170 -0
- package/collections/ai-run.collection.js.map +1 -0
- package/collections/ai-terminal-conversation.collection.d.ts +2 -0
- package/collections/ai-terminal-conversation.collection.js +140 -0
- package/collections/ai-terminal-conversation.collection.js.map +1 -0
- package/collections/ai-terminal-issue-report.collection.d.ts +2 -0
- package/collections/ai-terminal-issue-report.collection.js +148 -0
- package/collections/ai-terminal-issue-report.collection.js.map +1 -0
- package/collections/ai-terminal-message.collection.d.ts +2 -0
- package/collections/ai-terminal-message.collection.js +121 -0
- package/collections/ai-terminal-message.collection.js.map +1 -0
- package/collections/app-setting.collection.d.ts +3 -0
- package/collections/app-setting.collection.js +103 -0
- package/collections/app-setting.collection.js.map +1 -0
- package/collections/app-status.collection.d.ts +3 -0
- package/collections/app-status.collection.js +57 -0
- package/collections/app-status.collection.js.map +1 -0
- package/collections/communication-metric.collection.d.ts +2 -0
- package/collections/communication-metric.collection.js +133 -0
- package/collections/communication-metric.collection.js.map +1 -0
- package/collections/counter.collection.d.ts +3 -0
- package/collections/counter.collection.js +56 -0
- package/collections/counter.collection.js.map +1 -0
- package/collections/cron-job-history.collection.d.ts +3 -0
- package/collections/cron-job-history.collection.js +137 -0
- package/collections/cron-job-history.collection.js.map +1 -0
- package/collections/cron-job.collection.d.ts +3 -0
- package/collections/cron-job.collection.js +92 -0
- package/collections/cron-job.collection.js.map +1 -0
- package/collections/customer-notification.collection.d.ts +3 -0
- package/collections/customer-notification.collection.js +130 -0
- package/collections/customer-notification.collection.js.map +1 -0
- package/collections/customer-portal-password.collection.d.ts +3 -0
- package/collections/customer-portal-password.collection.js +75 -0
- package/collections/customer-portal-password.collection.js.map +1 -0
- package/collections/email-history.collection.d.ts +3 -0
- package/collections/email-history.collection.js +134 -0
- package/collections/email-history.collection.js.map +1 -0
- package/collections/email-verified.collection.d.ts +3 -0
- package/collections/email-verified.collection.js +62 -0
- package/collections/email-verified.collection.js.map +1 -0
- package/collections/file.collection.d.ts +3 -0
- package/collections/file.collection.js +74 -0
- package/collections/file.collection.js.map +1 -0
- package/collections/flag-update.collection.d.ts +3 -0
- package/collections/flag-update.collection.js +57 -0
- package/collections/flag-update.collection.js.map +1 -0
- package/collections/flag.collection.d.ts +3 -0
- package/collections/flag.collection.js +57 -0
- package/collections/flag.collection.js.map +1 -0
- package/collections/log-method-latency.collection.d.ts +3 -0
- package/collections/log-method-latency.collection.js +77 -0
- package/collections/log-method-latency.collection.js.map +1 -0
- package/collections/log-subscription.collection.d.ts +3 -0
- package/collections/log-subscription.collection.js +80 -0
- package/collections/log-subscription.collection.js.map +1 -0
- package/collections/log.collection.d.ts +3 -0
- package/collections/log.collection.js +93 -0
- package/collections/log.collection.js.map +1 -0
- package/collections/logged-in-users.collection.d.ts +3 -0
- package/collections/logged-in-users.collection.js +67 -0
- package/collections/logged-in-users.collection.js.map +1 -0
- package/collections/monitor-cpu.collection.d.ts +3 -0
- package/collections/monitor-cpu.collection.js +65 -0
- package/collections/monitor-cpu.collection.js.map +1 -0
- package/collections/monitor-function.collection.d.ts +3 -0
- package/collections/monitor-function.collection.js +74 -0
- package/collections/monitor-function.collection.js.map +1 -0
- package/collections/monitor-memory.collection.d.ts +3 -0
- package/collections/monitor-memory.collection.js +77 -0
- package/collections/monitor-memory.collection.js.map +1 -0
- package/collections/monitor-mongo.collection.d.ts +3 -0
- package/collections/monitor-mongo.collection.js +71 -0
- package/collections/monitor-mongo.collection.js.map +1 -0
- package/collections/notification.collection.d.ts +3 -0
- package/collections/notification.collection.js +57 -0
- package/collections/notification.collection.js.map +1 -0
- package/collections/openai-usage-ledger.collection.d.ts +2 -0
- package/collections/openai-usage-ledger.collection.js +188 -0
- package/collections/openai-usage-ledger.collection.js.map +1 -0
- package/collections/report-builder-dashboard-builder.collection.d.ts +3 -0
- package/collections/report-builder-dashboard-builder.collection.js +109 -0
- package/collections/report-builder-dashboard-builder.collection.js.map +1 -0
- package/collections/report-builder-library.collection.d.ts +3 -0
- package/collections/report-builder-library.collection.js +87 -0
- package/collections/report-builder-library.collection.js.map +1 -0
- package/collections/report-builder-report.collection.d.ts +4 -0
- package/collections/report-builder-report.collection.js +184 -0
- package/collections/report-builder-report.collection.js.map +1 -0
- package/collections/user-group.collection.d.ts +4 -0
- package/collections/user-group.collection.js +89 -0
- package/collections/user-group.collection.js.map +1 -0
- package/collections/user-guide.collection.d.ts +3 -0
- package/collections/user-guide.collection.js +57 -0
- package/collections/user-guide.collection.js.map +1 -0
- package/collections/user.collection.d.ts +4 -0
- package/collections/user.collection.js +180 -0
- package/collections/user.collection.js.map +1 -0
- package/cron/cron.d.ts +14 -0
- package/cron/cron.js +216 -0
- package/cron/cron.js.map +1 -0
- package/fixtures/cron-jobs.d.ts +1 -0
- package/fixtures/cron-jobs.js +150 -0
- package/fixtures/cron-jobs.js.map +1 -0
- package/fixtures/init.d.ts +1 -0
- package/fixtures/init.js +91 -0
- package/fixtures/init.js.map +1 -0
- package/http/auth.d.ts +2 -0
- package/http/auth.js +951 -0
- package/http/auth.js.map +1 -0
- package/http/health.d.ts +1 -0
- package/http/health.js +11 -0
- package/http/health.js.map +1 -0
- package/http/home.d.ts +1 -0
- package/http/home.js +134 -0
- package/http/home.js.map +1 -0
- package/http/slow-query-publication.d.ts +2 -0
- package/http/slow-query-publication.js +99 -0
- package/http/slow-query-publication.js.map +1 -0
- package/index.d.ts +1 -0
- package/index.js +19 -0
- package/index.js.map +1 -0
- package/managers/ai-assistant-codex-manager.manager.d.ts +67 -0
- package/managers/ai-assistant-codex-manager.manager.js +1113 -0
- package/managers/ai-assistant-codex-manager.manager.js.map +1 -0
- package/managers/ai-run-evidence.manager.d.ts +36 -0
- package/managers/ai-run-evidence.manager.js +377 -0
- package/managers/ai-run-evidence.manager.js.map +1 -0
- package/managers/communication-metric.manager.d.ts +16 -0
- package/managers/communication-metric.manager.js +134 -0
- package/managers/communication-metric.manager.js.map +1 -0
- package/managers/cron.manager.d.ts +20 -0
- package/managers/cron.manager.js +534 -0
- package/managers/cron.manager.js.map +1 -0
- package/managers/customer-notification-content.manager.d.ts +55 -0
- package/managers/customer-notification-content.manager.js +158 -0
- package/managers/customer-notification-content.manager.js.map +1 -0
- package/managers/diagnostic-manager-bootstrap.d.ts +9 -0
- package/managers/diagnostic-manager-bootstrap.js +260 -0
- package/managers/diagnostic-manager-bootstrap.js.map +1 -0
- package/managers/error-auto-fix.manager.d.ts +149 -0
- package/managers/error-auto-fix.manager.js +3064 -0
- package/managers/error-auto-fix.manager.js.map +1 -0
- package/managers/local-log.manager.d.ts +18 -0
- package/managers/local-log.manager.js +88 -0
- package/managers/local-log.manager.js.map +1 -0
- package/managers/method.manager.d.ts +84 -0
- package/managers/method.manager.js +1964 -0
- package/managers/method.manager.js.map +1 -0
- package/managers/mongo.manager.d.ts +224 -0
- package/managers/mongo.manager.js +5000 -0
- package/managers/mongo.manager.js.map +1 -0
- package/managers/monitor.manager.d.ts +70 -0
- package/managers/monitor.manager.js +550 -0
- package/managers/monitor.manager.js.map +1 -0
- package/managers/openai-usage-ledger.manager.d.ts +30 -0
- package/managers/openai-usage-ledger.manager.js +142 -0
- package/managers/openai-usage-ledger.manager.js.map +1 -0
- package/managers/slow-query-verifier.manager.d.ts +144 -0
- package/managers/slow-query-verifier.manager.js +3857 -0
- package/managers/slow-query-verifier.manager.js.map +1 -0
- package/managers/slow-query.manager.d.ts +28 -0
- package/managers/slow-query.manager.js +468 -0
- package/managers/slow-query.manager.js.map +1 -0
- package/managers/subscription.manager.d.ts +169 -0
- package/managers/subscription.manager.js +3434 -0
- package/managers/subscription.manager.js.map +1 -0
- package/managers/websocket.manager.d.ts +73 -0
- package/managers/websocket.manager.js +673 -0
- package/managers/websocket.manager.js.map +1 -0
- package/managers/worker-dispatcher.manager.d.ts +120 -0
- package/managers/worker-dispatcher.manager.js +1266 -0
- package/managers/worker-dispatcher.manager.js.map +1 -0
- package/managers/worker-server.manager.d.ts +35 -0
- package/managers/worker-server.manager.js +582 -0
- package/managers/worker-server.manager.js.map +1 -0
- package/methods/accounts.d.ts +2 -0
- package/methods/accounts.js +624 -0
- package/methods/accounts.js.map +1 -0
- package/methods/ai-terminal.d.ts +458 -0
- package/methods/ai-terminal.js +27991 -0
- package/methods/ai-terminal.js.map +1 -0
- package/methods/app-settings.d.ts +2 -0
- package/methods/app-settings.js +169 -0
- package/methods/app-settings.js.map +1 -0
- package/methods/aws.d.ts +2 -0
- package/methods/aws.js +877 -0
- package/methods/aws.js.map +1 -0
- package/methods/collections.d.ts +2 -0
- package/methods/collections.js +719 -0
- package/methods/collections.js.map +1 -0
- package/methods/counters.d.ts +2 -0
- package/methods/counters.js +113 -0
- package/methods/counters.js.map +1 -0
- package/methods/cron-jobs.d.ts +2 -0
- package/methods/cron-jobs.js +2475 -0
- package/methods/cron-jobs.js.map +1 -0
- package/methods/customer-notifications.d.ts +2 -0
- package/methods/customer-notifications.js +528 -0
- package/methods/customer-notifications.js.map +1 -0
- package/methods/diagnostics.d.ts +2 -0
- package/methods/diagnostics.js +703 -0
- package/methods/diagnostics.js.map +1 -0
- package/methods/flag-updates.d.ts +2 -0
- package/methods/flag-updates.js +8 -0
- package/methods/flag-updates.js.map +1 -0
- package/methods/flags.d.ts +2 -0
- package/methods/flags.js +8 -0
- package/methods/flags.js.map +1 -0
- package/methods/logs.d.ts +2 -0
- package/methods/logs.js +751 -0
- package/methods/logs.js.map +1 -0
- package/methods/mongo-explorer.d.ts +2 -0
- package/methods/mongo-explorer.js +1808 -0
- package/methods/mongo-explorer.js.map +1 -0
- package/methods/monitor.d.ts +2 -0
- package/methods/monitor.js +543 -0
- package/methods/monitor.js.map +1 -0
- package/methods/pdf.d.ts +2 -0
- package/methods/pdf.js +1216 -0
- package/methods/pdf.js.map +1 -0
- package/methods/publications.d.ts +1 -0
- package/methods/publications.js +183 -0
- package/methods/publications.js.map +1 -0
- package/methods/report-builder.d.ts +2 -0
- package/methods/report-builder.js +3094 -0
- package/methods/report-builder.js.map +1 -0
- package/methods/support.d.ts +2 -0
- package/methods/support.js +430 -0
- package/methods/support.js.map +1 -0
- package/models/ai-run.model.d.ts +19 -0
- package/models/ai-run.model.js +4 -0
- package/models/ai-run.model.js.map +1 -0
- package/models/ai-terminal-conversation.model.d.ts +17 -0
- package/models/ai-terminal-conversation.model.js +4 -0
- package/models/ai-terminal-conversation.model.js.map +1 -0
- package/models/ai-terminal-issue-report.model.d.ts +19 -0
- package/models/ai-terminal-issue-report.model.js +4 -0
- package/models/ai-terminal-issue-report.model.js.map +1 -0
- package/models/ai-terminal-message.model.d.ts +22 -0
- package/models/ai-terminal-message.model.js +4 -0
- package/models/ai-terminal-message.model.js.map +1 -0
- package/models/app-setting.model.d.ts +16 -0
- package/models/app-setting.model.js +4 -0
- package/models/app-setting.model.js.map +1 -0
- package/{src/models/app-status.model.ts → models/app-status.model.d.ts} +2 -3
- package/models/app-status.model.js +4 -0
- package/models/app-status.model.js.map +1 -0
- package/{src/models/billing-logged-in-users.model.ts → models/billing-logged-in-users.model.d.ts} +4 -5
- package/models/billing-logged-in-users.model.js +4 -0
- package/models/billing-logged-in-users.model.js.map +1 -0
- package/models/collection-document.model.d.ts +21 -0
- package/models/collection-document.model.js +4 -0
- package/models/collection-document.model.js.map +1 -0
- package/models/communication-metric.model.d.ts +20 -0
- package/models/communication-metric.model.js +4 -0
- package/models/communication-metric.model.js.map +1 -0
- package/{src/models/counter.model.ts → models/counter.model.d.ts} +3 -4
- package/models/counter.model.js +4 -0
- package/models/counter.model.js.map +1 -0
- package/models/cron-job-history.model.d.ts +15 -0
- package/models/cron-job-history.model.js +4 -0
- package/models/cron-job-history.model.js.map +1 -0
- package/models/cron-job.model.d.ts +14 -0
- package/models/cron-job.model.js +4 -0
- package/models/cron-job.model.js.map +1 -0
- package/models/customer-notification.model.d.ts +26 -0
- package/models/customer-notification.model.js +4 -0
- package/models/customer-notification.model.js.map +1 -0
- package/models/customer-portal-password.model.d.ts +11 -0
- package/models/customer-portal-password.model.js +4 -0
- package/models/customer-portal-password.model.js.map +1 -0
- package/models/dialog.model.d.ts +23 -0
- package/models/dialog.model.js +4 -0
- package/models/dialog.model.js.map +1 -0
- package/models/email-history.model.d.ts +32 -0
- package/{src/models/email-history.model.ts → models/email-history.model.js} +4 -36
- package/models/email-history.model.js.map +1 -0
- package/{src/models/email-verified.model.ts → models/email-verified.model.d.ts} +5 -6
- package/models/email-verified.model.js +4 -0
- package/models/email-verified.model.js.map +1 -0
- package/{src/models/file.model.ts → models/file.model.d.ts} +7 -8
- package/models/file.model.js +4 -0
- package/models/file.model.js.map +1 -0
- package/{src/models/flag-update.model.ts → models/flag-update.model.d.ts} +3 -4
- package/models/flag-update.model.js +4 -0
- package/models/flag-update.model.js.map +1 -0
- package/{src/models/flag.model.ts → models/flag.model.d.ts} +3 -4
- package/models/flag.model.js +4 -0
- package/models/flag.model.js.map +1 -0
- package/models/log-method-latency.model.d.ts +10 -0
- package/models/log-method-latency.model.js +4 -0
- package/models/log-method-latency.model.js.map +1 -0
- package/{src/models/log-subscription.model.ts → models/log-subscription.model.d.ts} +9 -11
- package/models/log-subscription.model.js +4 -0
- package/models/log-subscription.model.js.map +1 -0
- package/models/log.model.d.ts +17 -0
- package/models/log.model.js +4 -0
- package/models/log.model.js.map +1 -0
- package/{src/models/logged-in-users.model.ts → models/logged-in-users.model.d.ts} +5 -6
- package/models/logged-in-users.model.js +4 -0
- package/models/logged-in-users.model.js.map +1 -0
- package/{src/models/method-response.model.ts → models/method-response.model.d.ts} +6 -7
- package/models/method-response.model.js +4 -0
- package/models/method-response.model.js.map +1 -0
- package/models/method.model.d.ts +26 -0
- package/models/method.model.js +4 -0
- package/models/method.model.js.map +1 -0
- package/{src/models/monitor-cpu.model.ts → models/monitor-cpu.model.d.ts} +7 -9
- package/models/monitor-cpu.model.js +4 -0
- package/models/monitor-cpu.model.js.map +1 -0
- package/models/monitor-function.model.d.ts +14 -0
- package/models/monitor-function.model.js +4 -0
- package/models/monitor-function.model.js.map +1 -0
- package/models/monitor-memory.model.d.ts +15 -0
- package/models/monitor-memory.model.js +4 -0
- package/models/monitor-memory.model.js.map +1 -0
- package/models/monitor-mongo.model.d.ts +13 -0
- package/models/monitor-mongo.model.js +4 -0
- package/models/monitor-mongo.model.js.map +1 -0
- package/{src/models/notification.model.ts → models/notification.model.d.ts} +4 -6
- package/models/notification.model.js +4 -0
- package/models/notification.model.js.map +1 -0
- package/models/openai-usage-ledger.model.d.ts +30 -0
- package/models/openai-usage-ledger.model.js +4 -0
- package/models/openai-usage-ledger.model.js.map +1 -0
- package/models/pagination.model.d.ts +11 -0
- package/models/pagination.model.js +28 -0
- package/models/pagination.model.js.map +1 -0
- package/models/permission.model.d.ts +12 -0
- package/models/permission.model.js +4 -0
- package/models/permission.model.js.map +1 -0
- package/models/report-builder-dashboard-builder.model.d.ts +25 -0
- package/models/report-builder-dashboard-builder.model.js +4 -0
- package/models/report-builder-dashboard-builder.model.js.map +1 -0
- package/models/report-builder-library.model.d.ts +17 -0
- package/models/report-builder-library.model.js +4 -0
- package/models/report-builder-library.model.js.map +1 -0
- package/models/report-builder-report.model.d.ts +121 -0
- package/models/report-builder-report.model.js +4 -0
- package/models/report-builder-report.model.js.map +1 -0
- package/models/report-builder.model.d.ts +61 -0
- package/models/report-builder.model.js +4 -0
- package/models/report-builder.model.js.map +1 -0
- package/models/select-data-label.model.d.ts +9 -0
- package/models/select-data-label.model.js +4 -0
- package/models/select-data-label.model.js.map +1 -0
- package/models/server-message.model.d.ts +32 -0
- package/models/server-message.model.js +4 -0
- package/models/server-message.model.js.map +1 -0
- package/models/slow-query-report.model.d.ts +23 -0
- package/models/slow-query-report.model.js +4 -0
- package/models/slow-query-report.model.js.map +1 -0
- package/models/subscription.model.d.ts +31 -0
- package/models/subscription.model.js +4 -0
- package/models/subscription.model.js.map +1 -0
- package/models/support-ticket.model.d.ts +87 -0
- package/models/support-ticket.model.js +4 -0
- package/models/support-ticket.model.js.map +1 -0
- package/models/user-group.model.d.ts +20 -0
- package/models/user-group.model.js +4 -0
- package/models/user-group.model.js.map +1 -0
- package/{src/models/user-guide.model.ts → models/user-guide.model.d.ts} +4 -5
- package/models/user-guide.model.js +4 -0
- package/models/user-guide.model.js.map +1 -0
- package/models/user.model.d.ts +84 -0
- package/models/user.model.js +4 -0
- package/models/user.model.js.map +1 -0
- package/package.json +1 -1
- package/private/images/ResolveIO.png +0 -0
- package/public_api.js +127 -0
- package/public_api.js.map +1 -0
- package/publications/ai-terminal.d.ts +1 -0
- package/publications/ai-terminal.js +122 -0
- package/publications/ai-terminal.js.map +1 -0
- package/publications/app-settings.d.ts +2 -0
- package/publications/app-settings.js +28 -0
- package/publications/app-settings.js.map +1 -0
- package/publications/app-status.d.ts +2 -0
- package/publications/app-status.js +16 -0
- package/publications/app-status.js.map +1 -0
- package/publications/cron-jobs.d.ts +2 -0
- package/publications/cron-jobs.js +88 -0
- package/publications/cron-jobs.js.map +1 -0
- package/publications/customer-notifications.d.ts +2 -0
- package/publications/customer-notifications.js +161 -0
- package/publications/customer-notifications.js.map +1 -0
- package/publications/files.d.ts +2 -0
- package/publications/files.js +36 -0
- package/publications/files.js.map +1 -0
- package/publications/flags-update.d.ts +2 -0
- package/publications/flags-update.js +22 -0
- package/publications/flags-update.js.map +1 -0
- package/publications/flags.d.ts +2 -0
- package/publications/flags.js +22 -0
- package/publications/flags.js.map +1 -0
- package/publications/logs.d.ts +2 -0
- package/publications/logs.js +164 -0
- package/publications/logs.js.map +1 -0
- package/publications/notifications.d.ts +2 -0
- package/publications/notifications.js +16 -0
- package/publications/notifications.js.map +1 -0
- package/publications/report-builder-dashboard-builders.d.ts +2 -0
- package/publications/report-builder-dashboard-builders.js +42 -0
- package/publications/report-builder-dashboard-builders.js.map +1 -0
- package/publications/report-builder-libraries.d.ts +2 -0
- package/publications/report-builder-libraries.js +90 -0
- package/publications/report-builder-libraries.js.map +1 -0
- package/publications/report-builder-reports.d.ts +2 -0
- package/publications/report-builder-reports.js +50 -0
- package/publications/report-builder-reports.js.map +1 -0
- package/publications/super-admin.d.ts +2 -0
- package/publications/super-admin.js +16 -0
- package/publications/super-admin.js.map +1 -0
- package/publications/user-groups.d.ts +1 -0
- package/publications/user-groups.js +16 -0
- package/publications/user-groups.js.map +1 -0
- package/publications/user-guides.d.ts +1 -0
- package/publications/user-guides.js +16 -0
- package/publications/user-guides.js.map +1 -0
- package/resolveio-server-app.d.ts +70 -0
- package/resolveio-server-app.js +801 -0
- package/resolveio-server-app.js.map +1 -0
- package/server-app.d.ts +228 -0
- package/server-app.js +3566 -0
- package/server-app.js.map +1 -0
- package/services/codex-client.d.ts +128 -0
- package/services/codex-client.js +1629 -0
- package/services/codex-client.js.map +1 -0
- package/services/openai-client.d.ts +46 -0
- package/services/openai-client.js +318 -0
- package/services/openai-client.js.map +1 -0
- package/types/error-report.d.ts +25 -0
- package/types/error-report.js +4 -0
- package/types/error-report.js.map +1 -0
- package/types/slow-query-report.d.ts +27 -0
- package/types/slow-query-report.js +6 -0
- package/types/slow-query-report.js.map +1 -0
- package/util/ai-qa-policy.d.ts +124 -0
- package/util/ai-qa-policy.js +736 -0
- package/util/ai-qa-policy.js.map +1 -0
- package/util/ai-run-evidence-adapters.d.ts +109 -0
- package/util/ai-run-evidence-adapters.js +7234 -0
- package/util/ai-run-evidence-adapters.js.map +1 -0
- package/util/ai-run-evidence-dashboard.d.ts +88 -0
- package/util/ai-run-evidence-dashboard.js +343 -0
- package/util/ai-run-evidence-dashboard.js.map +1 -0
- package/util/ai-run-evidence-eval.d.ts +86 -0
- package/util/ai-run-evidence-eval.js +1018 -0
- package/util/ai-run-evidence-eval.js.map +1 -0
- package/util/ai-run-evidence.d.ts +244 -0
- package/util/ai-run-evidence.js +1096 -0
- package/util/ai-run-evidence.js.map +1 -0
- package/util/ai-runner-artifacts.d.ts +82 -0
- package/util/ai-runner-artifacts.js +713 -0
- package/util/ai-runner-artifacts.js.map +1 -0
- package/util/ai-runner-manager-autopilot.d.ts +210 -0
- package/util/ai-runner-manager-autopilot.js +642 -0
- package/util/ai-runner-manager-autopilot.js.map +1 -0
- package/util/ai-runner-manager-policy.d.ts +807 -0
- package/util/ai-runner-manager-policy.js +3501 -0
- package/util/ai-runner-manager-policy.js.map +1 -0
- package/util/ai-runner-qa-auth.d.ts +5 -0
- package/util/ai-runner-qa-auth.js +839 -0
- package/util/ai-runner-qa-auth.js.map +1 -0
- package/util/ai-runner-qa-tools.d.ts +26 -0
- package/util/ai-runner-qa-tools.js +3520 -0
- package/util/ai-runner-qa-tools.js.map +1 -0
- package/util/aicoder-runner-v6.d.ts +426 -0
- package/util/aicoder-runner-v6.js +2464 -0
- package/util/aicoder-runner-v6.js.map +1 -0
- package/util/common.d.ts +31 -0
- package/util/common.js +683 -0
- package/util/common.js.map +1 -0
- package/util/customer-portal-password.d.ts +13 -0
- package/util/customer-portal-password.js +209 -0
- package/util/customer-portal-password.js.map +1 -0
- package/util/error-reporter.d.ts +52 -0
- package/util/error-reporter.js +326 -0
- package/util/error-reporter.js.map +1 -0
- package/util/error-tracking.d.ts +13 -0
- package/util/error-tracking.js +120 -0
- package/util/error-tracking.js.map +1 -0
- package/util/openai-usage-cost.d.ts +6 -0
- package/util/openai-usage-cost.js +103 -0
- package/util/openai-usage-cost.js.map +1 -0
- package/util/report-builder-unwinds.d.ts +15 -0
- package/util/report-builder-unwinds.js +156 -0
- package/util/report-builder-unwinds.js.map +1 -0
- package/util/runner-process-janitor.d.ts +27 -0
- package/util/runner-process-janitor.js +208 -0
- package/util/runner-process-janitor.js.map +1 -0
- package/util/schema-report-builder.d.ts +6 -0
- package/util/schema-report-builder.js +481 -0
- package/util/schema-report-builder.js.map +1 -0
- package/util/slow-query-reporter.d.ts +28 -0
- package/util/slow-query-reporter.js +226 -0
- package/util/slow-query-reporter.js.map +1 -0
- package/util/subscription-dependency-context.d.ts +34 -0
- package/util/subscription-dependency-context.js +1283 -0
- package/util/subscription-dependency-context.js.map +1 -0
- package/util/support-runner-v5.d.ts +1426 -0
- package/util/support-runner-v5.js +7643 -0
- package/util/support-runner-v5.js.map +1 -0
- package/util/tokenizer.d.ts +5 -0
- package/util/tokenizer.js +41 -0
- package/util/tokenizer.js.map +1 -0
- package/workers/codex-runner.worker.d.ts +1 -0
- package/workers/codex-runner.worker.js +192 -0
- package/workers/codex-runner.worker.js.map +1 -0
- package/.nodemon.json +0 -5
- package/.vscode/settings.json +0 -21
- package/AGENTS.md +0 -195
- package/README.md +0 -22
- package/build_package.sh +0 -5
- package/compileDTS.pl +0 -64
- package/docs/ai-assistant-nightly-eval.md +0 -65
- package/docs/ai-assistant-preflight-checklist.md +0 -23
- package/docs/ai-assistant-report-builder-bridge-playbook.md +0 -115
- package/eslint-plugin-custom/index.js +0 -7
- package/eslint-plugin-custom/rules/no-filter-zero-index.js +0 -44
- package/eslint.config.js +0 -103
- package/gulpfile.js +0 -216
- package/methodAndPublicationListGenerator.py +0 -375
- package/mongodbensurers.js +0 -2
- package/mongostop.js +0 -3
- package/scripts/cleanup-bypassed-callmethod-logs.js +0 -616
- package/settings.development.json +0 -25
- package/settings.development.redacted.json +0 -25
- package/src/.env +0 -12
- package/src/ai/assistant-core-heuristics.ts +0 -379
- package/src/ai/resolveio-platform-intelligence-memory-corpus.ts +0 -185
- package/src/ai/resolveio-platform-intelligence-memory.ts +0 -325
- package/src/ai/resolveio-platform-intelligence.ts +0 -462
- package/src/client-server-app.ts +0 -12
- package/src/collections/ai-run.collection.ts +0 -117
- package/src/collections/ai-terminal-conversation.collection.ts +0 -91
- package/src/collections/ai-terminal-issue-report.collection.ts +0 -99
- package/src/collections/ai-terminal-message.collection.ts +0 -77
- package/src/collections/app-setting.collection.ts +0 -104
- package/src/collections/app-status.collection.ts +0 -58
- package/src/collections/communication-metric.collection.ts +0 -84
- package/src/collections/counter.collection.ts +0 -56
- package/src/collections/cron-job-history.collection.ts +0 -94
- package/src/collections/cron-job.collection.ts +0 -92
- package/src/collections/customer-notification.collection.ts +0 -131
- package/src/collections/customer-portal-password.collection.ts +0 -76
- package/src/collections/email-history.collection.ts +0 -134
- package/src/collections/email-verified.collection.ts +0 -62
- package/src/collections/file.collection.ts +0 -74
- package/src/collections/flag-update.collection.ts +0 -57
- package/src/collections/flag.collection.ts +0 -57
- package/src/collections/log-method-latency.collection.ts +0 -77
- package/src/collections/log-subscription.collection.ts +0 -80
- package/src/collections/log.collection.ts +0 -93
- package/src/collections/logged-in-users.collection.ts +0 -67
- package/src/collections/monitor-cpu.collection.ts +0 -65
- package/src/collections/monitor-function.collection.ts +0 -74
- package/src/collections/monitor-memory.collection.ts +0 -77
- package/src/collections/monitor-mongo.collection.ts +0 -71
- package/src/collections/notification.collection.ts +0 -57
- package/src/collections/openai-usage-ledger.collection.ts +0 -131
- package/src/collections/report-builder-dashboard-builder.collection.ts +0 -109
- package/src/collections/report-builder-library.collection.ts +0 -89
- package/src/collections/report-builder-report.collection.ts +0 -184
- package/src/collections/user-group.collection.ts +0 -89
- package/src/collections/user-guide.collection.ts +0 -57
- package/src/collections/user.collection.ts +0 -181
- package/src/cron/cron.ts +0 -117
- package/src/fixtures/cron-jobs.ts +0 -95
- package/src/fixtures/init.ts +0 -35
- package/src/http/auth.ts +0 -818
- package/src/http/health.ts +0 -7
- package/src/http/home.ts +0 -90
- package/src/http/slow-query-publication.ts +0 -49
- package/src/index.ts +0 -1
- package/src/managers/ai-assistant-codex-manager.manager.ts +0 -1131
- package/src/managers/ai-run-evidence.manager.ts +0 -264
- package/src/managers/communication-metric.manager.ts +0 -82
- package/src/managers/cron.manager.ts +0 -333
- package/src/managers/customer-notification-content.manager.ts +0 -236
- package/src/managers/diagnostic-manager-bootstrap.ts +0 -165
- package/src/managers/error-auto-fix.manager.ts +0 -2767
- package/src/managers/local-log.manager.ts +0 -113
- package/src/managers/method.manager.ts +0 -1857
- package/src/managers/mongo.manager.ts +0 -4575
- package/src/managers/monitor.manager.ts +0 -507
- package/src/managers/openai-usage-ledger.manager.ts +0 -112
- package/src/managers/slow-query-verifier.manager.ts +0 -3590
- package/src/managers/slow-query.manager.ts +0 -519
- package/src/managers/subscription.manager.ts +0 -3128
- package/src/managers/websocket.manager.ts +0 -746
- package/src/managers/worker-dispatcher.manager.ts +0 -1360
- package/src/managers/worker-server.manager.ts +0 -536
- package/src/methods/accounts.ts +0 -532
- package/src/methods/ai-terminal.ts +0 -29070
- package/src/methods/app-settings.ts +0 -114
- package/src/methods/aws.ts +0 -649
- package/src/methods/collections.ts +0 -641
- package/src/methods/counters.ts +0 -69
- package/src/methods/cron-jobs.ts +0 -2614
- package/src/methods/customer-notifications.ts +0 -458
- package/src/methods/diagnostics.ts +0 -616
- package/src/methods/flag-updates.ts +0 -7
- package/src/methods/flags.ts +0 -7
- package/src/methods/logs.ts +0 -657
- package/src/methods/mongo-explorer.ts +0 -1880
- package/src/methods/monitor.ts +0 -540
- package/src/methods/pdf.ts +0 -1236
- package/src/methods/publications.ts +0 -129
- package/src/methods/report-builder.ts +0 -3300
- package/src/methods/support.ts +0 -335
- package/src/models/ai-run.model.ts +0 -27
- package/src/models/ai-terminal-conversation.model.ts +0 -19
- package/src/models/ai-terminal-issue-report.model.ts +0 -21
- package/src/models/ai-terminal-message.model.ts +0 -24
- package/src/models/app-setting.model.ts +0 -17
- package/src/models/collection-document.model.ts +0 -24
- package/src/models/communication-metric.model.ts +0 -23
- package/src/models/cron-job-history.model.ts +0 -16
- package/src/models/cron-job.model.ts +0 -15
- package/src/models/customer-notification.model.ts +0 -28
- package/src/models/customer-portal-password.model.ts +0 -12
- package/src/models/dialog.model.ts +0 -25
- package/src/models/log-method-latency.model.ts +0 -11
- package/src/models/log.model.ts +0 -19
- package/src/models/method.model.ts +0 -25
- package/src/models/monitor-function.model.ts +0 -16
- package/src/models/monitor-memory.model.ts +0 -17
- package/src/models/monitor-mongo.model.ts +0 -15
- package/src/models/openai-usage-ledger.model.ts +0 -56
- package/src/models/pagination.model.ts +0 -35
- package/src/models/permission.model.ts +0 -14
- package/src/models/report-builder-dashboard-builder.model.ts +0 -29
- package/src/models/report-builder-library.model.ts +0 -20
- package/src/models/report-builder-report.model.ts +0 -136
- package/src/models/report-builder.model.ts +0 -68
- package/src/models/select-data-label.model.ts +0 -9
- package/src/models/server-message.model.ts +0 -31
- package/src/models/slow-query-report.model.ts +0 -23
- package/src/models/subscription.model.ts +0 -73
- package/src/models/support-ticket.model.ts +0 -104
- package/src/models/user-group.model.ts +0 -24
- package/src/models/user.model.ts +0 -96
- package/src/private/images/ResolveIO.png +0 -0
- package/src/publications/ai-terminal.ts +0 -73
- package/src/publications/app-settings.ts +0 -25
- package/src/publications/app-status.ts +0 -13
- package/src/publications/cron-jobs.ts +0 -40
- package/src/publications/customer-notifications.ts +0 -101
- package/src/publications/files.ts +0 -33
- package/src/publications/flags-update.ts +0 -19
- package/src/publications/flags.ts +0 -19
- package/src/publications/logs.ts +0 -163
- package/src/publications/notifications.ts +0 -13
- package/src/publications/report-builder-dashboard-builders.ts +0 -39
- package/src/publications/report-builder-libraries.ts +0 -41
- package/src/publications/report-builder-reports.ts +0 -47
- package/src/publications/super-admin.ts +0 -13
- package/src/publications/user-groups.ts +0 -12
- package/src/publications/user-guides.ts +0 -12
- package/src/resolveio-server-app.ts +0 -617
- package/src/server-app.ts +0 -3354
- package/src/services/codex-client.ts +0 -1231
- package/src/services/openai-client.ts +0 -265
- package/src/types/error-report.ts +0 -26
- package/src/types/js-tiktoken.d.ts +0 -11
- package/src/types/slow-query-report.ts +0 -28
- package/src/util/ai-qa-policy.ts +0 -925
- package/src/util/ai-run-evidence-adapters.ts +0 -8347
- package/src/util/ai-run-evidence-dashboard.ts +0 -323
- package/src/util/ai-run-evidence-eval.ts +0 -1057
- package/src/util/ai-run-evidence.ts +0 -1430
- package/src/util/ai-runner-artifacts.ts +0 -586
- package/src/util/ai-runner-manager-autopilot.ts +0 -961
- package/src/util/ai-runner-manager-policy.ts +0 -5011
- package/src/util/ai-runner-qa-auth.ts +0 -838
- package/src/util/ai-runner-qa-tools.ts +0 -3536
- package/src/util/aicoder-runner-v6.ts +0 -3121
- package/src/util/common.ts +0 -649
- package/src/util/customer-portal-password.ts +0 -183
- package/src/util/error-reporter.ts +0 -332
- package/src/util/error-tracking.ts +0 -79
- package/src/util/openai-usage-cost.ts +0 -114
- package/src/util/report-builder-unwinds.ts +0 -180
- package/src/util/runner-process-janitor.ts +0 -219
- package/src/util/schema-report-builder.ts +0 -448
- package/src/util/slow-query-reporter.ts +0 -216
- package/src/util/subscription-dependency-context.ts +0 -1096
- package/src/util/support-runner-v5.ts +0 -10040
- package/src/util/tokenizer.ts +0 -38
- package/src/workers/codex-runner.worker.ts +0 -142
- package/start_server.sh +0 -5
- package/tests/ai-assistant-corpus-build.ts +0 -484
- package/tests/ai-assistant-corpus-replay-e2e.ts +0 -774
- package/tests/ai-assistant-data-parity-e2e.ts +0 -1989
- package/tests/ai-assistant-eval-triage.ts +0 -831
- package/tests/ai-assistant-openai-e2e.ts +0 -1061
- package/tests/ai-assistant-openai-git-e2e.ts +0 -155
- package/tests/ai-assistant-preflight-matrix.ts +0 -215
- package/tests/ai-assistant-routing-eval.test.ts +0 -585
- package/tests/ai-assistant-snf-live-eval.ts +0 -975
- package/tests/ai-assistant-utils.test.ts +0 -4834
- package/tests/ai-manager-autopilot-snapshot.test.ts +0 -193
- package/tests/ai-manager-recovery-checkpoint.test.ts +0 -1383
- package/tests/ai-run-eval.test.ts +0 -132
- package/tests/ai-run-evidence.test.ts +0 -3773
- package/tests/ai-runner-contract.test.ts +0 -515
- package/tests/aicoder-runner-v6.test.ts +0 -822
- package/tests/error-reporter.test.ts +0 -145
- package/tests/method-publication-generator.test.ts +0 -46
- package/tests/report-builder-linking.test.ts +0 -79
- package/tests/resolveio-platform-intelligence.test.ts +0 -352
- package/tests/server-app-cron-owner.test.ts +0 -127
- package/tests/subscription-connect-race.test.ts +0 -158
- package/tests/subscription-dependency-context.test.ts +0 -324
- package/tests/subscription-manager-collection-tracking.test.ts +0 -86
- package/tests/subscription-manager-invalidation.test.ts +0 -86
- package/tests/support-runner-v5.test.ts +0 -3201
- package/tsconfig.json +0 -34
- /package/{src/private → private}/email-templates/enrollment.html +0 -0
- /package/{src/private → private}/email-templates/forgot-password.html +0 -0
- /package/{src/private → private}/email-templates/support-ticket-deleted.html +0 -0
- /package/{src/private → private}/email-templates/support-ticket-modified.html +0 -0
- /package/{src/private → private}/email-templates/support-ticket.html +0 -0
- /package/{src/public_api.ts → public_api.d.ts} +0 -0
|
@@ -1,4834 +0,0 @@
|
|
|
1
|
-
import { ObjectId } from 'mongodb';
|
|
2
|
-
import {
|
|
3
|
-
buildAssistantChangeHistorySummaryFromCommits,
|
|
4
|
-
buildAssistantSystemPromptForTesting,
|
|
5
|
-
applyAssistantDisplayTableToResponseForTesting,
|
|
6
|
-
adjustAssistantClassifiedAppDataIntentForTesting,
|
|
7
|
-
buildAssistantToolFallbackResponseForTesting,
|
|
8
|
-
buildAssistantAnswerQualityFromExecution,
|
|
9
|
-
buildAssistantWorkspaceRootCandidates,
|
|
10
|
-
buildAssistantDatedPivotDisplay,
|
|
11
|
-
buildAssistantSchemaHints,
|
|
12
|
-
buildAssistantToolRequest,
|
|
13
|
-
deriveAssistantCommandExecutionStatus,
|
|
14
|
-
evaluateAssistantGuardrailsForTesting,
|
|
15
|
-
buildAssistantInvoiceCustomerLabelExpr,
|
|
16
|
-
buildDisplayTable,
|
|
17
|
-
executeAiAssistantMongoAggregate,
|
|
18
|
-
executeAiAssistantMongoRead,
|
|
19
|
-
extractAssistantMongoDirective,
|
|
20
|
-
expandAssistantFieldTokensForTesting,
|
|
21
|
-
flattenForTable,
|
|
22
|
-
formatDisplayTableMarkdown,
|
|
23
|
-
normalizeAssistantMonthlyCalendarWindowPipeline,
|
|
24
|
-
normalizeAssistantAggregatePipeline,
|
|
25
|
-
expandAggregateDateMatchFallbackForTesting,
|
|
26
|
-
normalizeAssistantNowExprPlaceholders,
|
|
27
|
-
repairAssistantPositionalPathSegmentsInPipeline,
|
|
28
|
-
repairAssistantFieldPathReferenceInPipeline,
|
|
29
|
-
normalizeIdsForTargetField,
|
|
30
|
-
rankAssistantNavigationRoutes,
|
|
31
|
-
resolveAssistantCollectionOverride,
|
|
32
|
-
resolveAssistantCollectionNameForTesting,
|
|
33
|
-
resolveCollectionOverrideWithContextForTesting,
|
|
34
|
-
resolveAssistantAvailableCrossCollectionFallbacksFromNames,
|
|
35
|
-
resolveAssistantAppCollectionHintCandidates,
|
|
36
|
-
buildAssistantDataRequestClassifierMessagesForTesting,
|
|
37
|
-
resolveAssistantHeuristicDirectiveForTesting,
|
|
38
|
-
resolveAssistantCrossCollectionFallbackCandidates,
|
|
39
|
-
resolveAssistantReportBuilderBridgeCollection,
|
|
40
|
-
isAssistantDeterministicHeuristicEnabledForApp,
|
|
41
|
-
shouldAcceptAssistantFallbackDocuments,
|
|
42
|
-
shouldEnforceAssistantDatedDirective,
|
|
43
|
-
shouldLockAssistantRequestedCollectionForExecutionForTesting,
|
|
44
|
-
shouldPreserveAssistantProbeCollectionForTesting,
|
|
45
|
-
rewriteMatchExpressionsToExpr,
|
|
46
|
-
rewriteEmbeddedMatchObjects,
|
|
47
|
-
resolveCodexThoughtLevel,
|
|
48
|
-
resolveAssistantReadDisplayMaxRows,
|
|
49
|
-
resolveReadMultiTermJobRegexFallbackForTesting,
|
|
50
|
-
serializeMongoValue,
|
|
51
|
-
shouldRunAssistantPlanner,
|
|
52
|
-
shouldUseAssistantChangeHistoryFastPath,
|
|
53
|
-
shouldUseAssistantNavigationFastPath,
|
|
54
|
-
stripQueryFieldPathsDeep,
|
|
55
|
-
stripScopedFieldsFromPipeline
|
|
56
|
-
} from '../src/methods/ai-terminal';
|
|
57
|
-
import { ResolveIOServer } from '../src/resolveio-server-app';
|
|
58
|
-
import * as UserCollection from '../src/collections/user.collection';
|
|
59
|
-
|
|
60
|
-
function assert(condition: boolean, message: string) {
|
|
61
|
-
if (!condition) {
|
|
62
|
-
throw new Error(message);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function testDirectiveParsing() {
|
|
67
|
-
const content = [
|
|
68
|
-
'Summary line',
|
|
69
|
-
'MONGO_READ: {"collection":"users","query":{"active":true},"options":{"limit":5}}',
|
|
70
|
-
'More context',
|
|
71
|
-
'MONGO_AGG: {"collection":"invoices","pipeline":[{"$match":{"status":"Open"}}]}'
|
|
72
|
-
].join('\n');
|
|
73
|
-
const result = extractAssistantMongoDirective(content);
|
|
74
|
-
assert(!!result, 'Expected directive to be detected');
|
|
75
|
-
assert(result?.type === 'aggregate', 'Expected last directive to win');
|
|
76
|
-
assert(result?.payload?.collection === 'invoices', 'Expected payload collection to parse');
|
|
77
|
-
assert(!result?.cleaned.includes('MONGO_READ'), 'Expected cleaned content to remove directives');
|
|
78
|
-
assert(!result?.cleaned.includes('MONGO_AGG'), 'Expected cleaned content to remove directives');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function testReportBuilderDirectiveParsing() {
|
|
82
|
-
const content = [
|
|
83
|
-
'Summary line',
|
|
84
|
-
'REPORT_BUILDER_READ: {"collection":"users","query":{"active":true},"options":{"limit":5}}',
|
|
85
|
-
'REPORT_BUILDER_AGG: {"collection":"invoices","pipeline":[{"$match":{"status":"Open"}}]}'
|
|
86
|
-
].join('\n');
|
|
87
|
-
const result = extractAssistantMongoDirective(content);
|
|
88
|
-
assert(!!result, 'Expected report builder directive to be detected');
|
|
89
|
-
assert(result?.type === 'aggregate', 'Expected report builder aggregate directive to win');
|
|
90
|
-
assert(result?.payload?.collection === 'invoices', 'Expected report builder payload collection to parse');
|
|
91
|
-
assert(!result?.cleaned.includes('REPORT_BUILDER_READ'), 'Expected cleaned content to remove report builder read directive');
|
|
92
|
-
assert(!result?.cleaned.includes('REPORT_BUILDER_AGG'), 'Expected cleaned content to remove report builder agg directive');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function testFlattenForTable() {
|
|
96
|
-
const flattened = flattenForTable({
|
|
97
|
-
_id: { status: 'Open', type: 'A' },
|
|
98
|
-
count: 3
|
|
99
|
-
}, { includeGroupFromId: true });
|
|
100
|
-
assert(flattened.status === 'Open', 'Expected _id.status flattened to status');
|
|
101
|
-
assert(flattened.type === 'A', 'Expected _id.type flattened to type');
|
|
102
|
-
assert(flattened.count === 3, 'Expected count retained');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function testDisplayTableDefaults() {
|
|
106
|
-
const docs = [
|
|
107
|
-
{
|
|
108
|
-
_id: { toHexString: () => 'abcdef123456' },
|
|
109
|
-
name: 'Alpha',
|
|
110
|
-
createdAt: new Date('2024-01-01T00:00:00.000Z'),
|
|
111
|
-
updatedAt: new Date('2024-01-02T00:00:00.000Z'),
|
|
112
|
-
id_client: 'C1',
|
|
113
|
-
__v: 0
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
_id: { toHexString: () => '123456abcdef' },
|
|
117
|
-
name: 'Beta',
|
|
118
|
-
createdAt: new Date('2024-01-03T00:00:00.000Z'),
|
|
119
|
-
updatedAt: new Date('2024-01-04T00:00:00.000Z'),
|
|
120
|
-
id_client: 'C1',
|
|
121
|
-
__v: 0
|
|
122
|
-
}
|
|
123
|
-
];
|
|
124
|
-
const display = buildDisplayTable(docs);
|
|
125
|
-
assert(display.columns.includes('Created At'), 'Expected Created At column');
|
|
126
|
-
assert(display.columns.includes('Updated At'), 'Expected Updated At column');
|
|
127
|
-
assert(!display.columns.some(col => col.toLowerCase() === 'id_client'), 'Expected id_client hidden');
|
|
128
|
-
assert(!display.columns.some(col => col.toLowerCase() === '_id'), 'Expected _id hidden');
|
|
129
|
-
assert(!display.columns.some(col => col.toLowerCase() === '__v'), 'Expected __v hidden');
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function testSerializeMongoValue() {
|
|
133
|
-
const value = serializeMongoValue(['A', 'B', 'C', 'D']);
|
|
134
|
-
assert(typeof value === 'string', 'Expected array to serialize to string');
|
|
135
|
-
assert(String(value).includes('...'), 'Expected array preview to be truncated');
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function testDisplayMarkdownTable() {
|
|
139
|
-
const display = buildDisplayTable([
|
|
140
|
-
{ name: 'Alpha', status: 'Active' },
|
|
141
|
-
{ name: 'Beta', status: 'Paused' }
|
|
142
|
-
]);
|
|
143
|
-
const markdown = formatDisplayTableMarkdown(display);
|
|
144
|
-
assert(markdown.includes('| name |') || markdown.includes('| Name |'), 'Expected markdown header row');
|
|
145
|
-
assert(markdown.split('\n').length >= 3, 'Expected markdown table rows');
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function testAssistantToolFallbackResponseIncludesUsefulLeadSummary() {
|
|
149
|
-
const response = buildAssistantToolFallbackResponseForTesting({
|
|
150
|
-
type: 'mongo_agg',
|
|
151
|
-
input: { collection: 'work-order-dynamics' },
|
|
152
|
-
output: {
|
|
153
|
-
collection: 'work-order-dynamics',
|
|
154
|
-
rowCount: 2,
|
|
155
|
-
columns: ['Customer', 'Active', 'Closed', 'Completed', 'Work Orders'],
|
|
156
|
-
display: {
|
|
157
|
-
columns: ['Customer', 'Active', 'Closed', 'Completed', 'Work Orders'],
|
|
158
|
-
rows: [
|
|
159
|
-
{ Customer: 'Avid', Active: 0, Closed: 4, Completed: 99, 'Work Orders': 217 },
|
|
160
|
-
{ Customer: 'Beta', Active: 0, Closed: 0, Completed: 10, 'Work Orders': 11 }
|
|
161
|
-
],
|
|
162
|
-
rowCount: 2,
|
|
163
|
-
truncated: false,
|
|
164
|
-
includeIds: false
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
assert(
|
|
169
|
-
response.includes('I found 2 matching rows from work-order-dynamics.'),
|
|
170
|
-
'Expected fallback response to state row count and source in plain language'
|
|
171
|
-
);
|
|
172
|
-
assert(
|
|
173
|
-
response.includes('Top row: Customer Avid, Work Orders 217.'),
|
|
174
|
-
'Expected fallback response to summarize the leading row before the table'
|
|
175
|
-
);
|
|
176
|
-
assert(response.includes('| Customer | Active | Closed | Completed | Work Orders |'), 'Expected fallback response to include table');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function testAssistantToolFallbackResponseIncludesRequestedBreakdowns() {
|
|
180
|
-
const response = buildAssistantToolFallbackResponseForTesting({
|
|
181
|
-
type: 'mongo_agg',
|
|
182
|
-
input: { collection: 'chemical-blends' },
|
|
183
|
-
output: {
|
|
184
|
-
collection: 'chemical-blends',
|
|
185
|
-
rowCount: 2,
|
|
186
|
-
columns: ['Chemical', '2026-06'],
|
|
187
|
-
display: {
|
|
188
|
-
columns: ['Chemical', '2026-06'],
|
|
189
|
-
rows: [
|
|
190
|
-
{ Chemical: 'NE-227', '2026-06': 7 },
|
|
191
|
-
{ Chemical: 'ME-350', '2026-06': 4 }
|
|
192
|
-
],
|
|
193
|
-
rowCount: 2,
|
|
194
|
-
truncated: false,
|
|
195
|
-
includeIds: false
|
|
196
|
-
},
|
|
197
|
-
debug: {
|
|
198
|
-
assumptions: [
|
|
199
|
-
'Sent means shipped quantity when users ask for delivered chemicals'
|
|
200
|
-
],
|
|
201
|
-
requestContract: {
|
|
202
|
-
requested_breakdowns: [
|
|
203
|
-
{ type: 'time', requested_text: 'month', granularity: 'month', field_hint: 'date' },
|
|
204
|
-
{ type: 'entity', requested_text: 'chemical', dimension: 'chemical', field_hints: ['chemical'] }
|
|
205
|
-
]
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
assert(
|
|
211
|
-
response.includes('- Summary breakdown: by month and chemical'),
|
|
212
|
-
'Expected fallback response to state all requested breakdown dimensions'
|
|
213
|
-
);
|
|
214
|
-
assert(
|
|
215
|
-
response.includes('- Assumptions: Sent means shipped quantity'),
|
|
216
|
-
'Expected fallback response to include configured assumptions even when rows are returned'
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function testAssistantToolFallbackResponseExplainsZeroRows() {
|
|
221
|
-
const response = buildAssistantToolFallbackResponseForTesting({
|
|
222
|
-
type: 'mongo_agg',
|
|
223
|
-
input: { collection: 'work-order-dynamics' },
|
|
224
|
-
output: {
|
|
225
|
-
collection: 'work-order-dynamics',
|
|
226
|
-
rowCount: 0,
|
|
227
|
-
columns: ['Customer', 'Work Orders'],
|
|
228
|
-
display: {
|
|
229
|
-
columns: ['Customer', 'Work Orders'],
|
|
230
|
-
rows: [],
|
|
231
|
-
rowCount: 0,
|
|
232
|
-
truncated: false,
|
|
233
|
-
includeIds: false
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
assert(
|
|
238
|
-
response.includes('I checked the data') && response.includes('no matching rows were returned.'),
|
|
239
|
-
'Expected zero-row fallback response to explain successful zero-result execution'
|
|
240
|
-
);
|
|
241
|
-
assert(response.includes('- Rows returned: 0'), 'Expected zero-row fallback response to include row count');
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function testBuildAssistantAnswerQualityFromExecution() {
|
|
245
|
-
const rowEvidence = buildAssistantAnswerQualityFromExecution({
|
|
246
|
-
requestMessage: 'Show revenue by month for the last 6 months.',
|
|
247
|
-
dataQuestion: true,
|
|
248
|
-
reportStyleIntent: 'dated',
|
|
249
|
-
requestedTimeGrain: 'month',
|
|
250
|
-
toolResult: {
|
|
251
|
-
type: 'mongo_agg',
|
|
252
|
-
input: { collection: 'invoices', pipeline: [{ $match: { status: 'Paid' } }] },
|
|
253
|
-
output: {
|
|
254
|
-
collection: 'invoices',
|
|
255
|
-
rowCount: 2,
|
|
256
|
-
columns: ['Month', 'Revenue'],
|
|
257
|
-
display: { columns: ['Month', 'Revenue'], rows: [{ Month: '2026-05', Revenue: 100 }], rowCount: 1 },
|
|
258
|
-
verification: {
|
|
259
|
-
metrics: {
|
|
260
|
-
window: {
|
|
261
|
-
startDate: '2025-12-01',
|
|
262
|
-
endDate: '2026-05-31',
|
|
263
|
-
months: 6,
|
|
264
|
-
type: 'full_month'
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
},
|
|
270
|
-
now: '2026-06-16T12:00:00.000Z'
|
|
271
|
-
});
|
|
272
|
-
assert(rowEvidence.queryStatus === 'ok', 'Expected successful tool execution to become ok queryStatus');
|
|
273
|
-
assert(rowEvidence.requiresDateWindow === true, 'Expected dated request to require date window evidence');
|
|
274
|
-
assert(rowEvidence.dateWindow?.startDate === '2025-12-01', 'Expected date-window start evidence');
|
|
275
|
-
assert(rowEvidence.dateWindow?.endDate === '2026-05-31', 'Expected date-window end evidence');
|
|
276
|
-
assert(rowEvidence.confidenceLevel === 'high', 'Expected successful data answer to be high confidence');
|
|
277
|
-
assert(Array.isArray(rowEvidence.citationRefs) && rowEvidence.citationRefs.length === 1, 'Expected tool evidence citation');
|
|
278
|
-
assert(rowEvidence.requiresQueryEvidence === true, 'Expected data answers to require structured query evidence');
|
|
279
|
-
assert(Array.isArray(rowEvidence.queryExecutions) && rowEvidence.queryExecutions.length === 1, 'Expected structured query execution proof');
|
|
280
|
-
assert(rowEvidence.queryExecutions[0].id === rowEvidence.citationRefs[0], 'Expected query execution id to match cited tool evidence');
|
|
281
|
-
assert(rowEvidence.queryExecutions[0].tool === 'aiAssistantMongoAggregate', 'Expected aggregate tool proof');
|
|
282
|
-
assert(rowEvidence.queryExecutions[0].resultCount === 2, 'Expected resultCount proof');
|
|
283
|
-
|
|
284
|
-
const noDataEvidence = buildAssistantAnswerQualityFromExecution({
|
|
285
|
-
requestMessage: 'Show invoices for this customer this month.',
|
|
286
|
-
dataQuestion: true,
|
|
287
|
-
toolResult: {
|
|
288
|
-
type: 'mongo_read',
|
|
289
|
-
input: { collection: 'invoices', query: { id_customer: 'cust-1' } },
|
|
290
|
-
output: {
|
|
291
|
-
collection: 'invoices',
|
|
292
|
-
rowCount: 0,
|
|
293
|
-
columns: ['Invoice'],
|
|
294
|
-
display: { columns: ['Invoice'], rows: [], rowCount: 0 }
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
now: '2026-06-16T12:00:00.000Z'
|
|
298
|
-
});
|
|
299
|
-
assert(noDataEvidence.queryStatus === 'no_data', 'Expected zero rows to become no_data');
|
|
300
|
-
assert(noDataEvidence.noDataConfirmed === true, 'Expected zero-row tool result to confirm no-data');
|
|
301
|
-
assert(noDataEvidence.confidenceLevel === 'medium', 'Expected no-data answer confidence to be medium');
|
|
302
|
-
assert(Array.isArray(noDataEvidence.queryExecutions) && noDataEvidence.queryExecutions.length === 1, 'Expected no-data answers to retain query execution proof');
|
|
303
|
-
assert(noDataEvidence.queryExecutions[0].queryStatus === 'no_data', 'Expected no-data query proof status');
|
|
304
|
-
assert(noDataEvidence.queryExecutions[0].resultCount === 0, 'Expected no-data resultCount proof');
|
|
305
|
-
assert(Array.isArray(noDataEvidence.queryEvidenceRefs) && noDataEvidence.queryEvidenceRefs.length === 1, 'Expected no-data query evidence refs');
|
|
306
|
-
|
|
307
|
-
const permissionEvidence = buildAssistantAnswerQualityFromExecution({
|
|
308
|
-
requestMessage: 'Show invoice totals.',
|
|
309
|
-
dataQuestion: true,
|
|
310
|
-
toolExecution: {
|
|
311
|
-
permissionDenied: true,
|
|
312
|
-
outcome: { error: 'AI assistant report builder bridge: Access denied.' }
|
|
313
|
-
},
|
|
314
|
-
now: '2026-06-16T12:00:00.000Z'
|
|
315
|
-
});
|
|
316
|
-
assert(permissionEvidence.queryStatus === 'permission_error', 'Expected permission failures to stay distinct from no-data');
|
|
317
|
-
assert(permissionEvidence.requiresHumanReview === true, 'Expected permission failures to require review');
|
|
318
|
-
assert(permissionEvidence.canAnswerCustomer === false, 'Expected permission failures to block customer answer');
|
|
319
|
-
assert(permissionEvidence.requiresQueryEvidence === true, 'Expected failed data answers to keep query-evidence requirement visible');
|
|
320
|
-
|
|
321
|
-
const nonDataEvidence = buildAssistantAnswerQualityFromExecution({
|
|
322
|
-
requestMessage: 'Say hello.',
|
|
323
|
-
dataQuestion: false,
|
|
324
|
-
now: '2026-06-16T12:00:00.000Z'
|
|
325
|
-
});
|
|
326
|
-
assert(nonDataEvidence.queryStatus === 'ok', 'Expected non-data assistant answer to stay ok without tools');
|
|
327
|
-
assert(nonDataEvidence.requiresQueryEvidence === false, 'Expected non-data answers to avoid query evidence requirement');
|
|
328
|
-
assert(!Array.isArray(nonDataEvidence.queryExecutions), 'Expected non-data answers to avoid synthetic query proof');
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function testAssistantZeroRowDisplayDoesNotAppendSyntheticTable() {
|
|
332
|
-
const response = applyAssistantDisplayTableToResponseForTesting(
|
|
333
|
-
'The query ran successfully against jobs, but no matching rows were returned.\n- Source: jobs\n- Rows returned: 0',
|
|
334
|
-
{
|
|
335
|
-
columns: ['Job', 'Customer', 'Chemical', 'Quantity Sent'],
|
|
336
|
-
rows: [],
|
|
337
|
-
rowCount: 0,
|
|
338
|
-
truncated: false,
|
|
339
|
-
includeIds: false
|
|
340
|
-
}
|
|
341
|
-
);
|
|
342
|
-
assert(!response.includes('| Job |'), 'Expected zero-row responses to omit synthetic display tables');
|
|
343
|
-
assert(!response.includes('No rows matched | 0'), 'Expected zero-row responses to avoid fake table rows');
|
|
344
|
-
assert(response.includes('Rows returned: 0'), 'Expected zero-row response summary to remain intact');
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
function testDisplayMarkdownTableDefaultsToShortDateFormat() {
|
|
348
|
-
const priorInline = process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
349
|
-
const priorFile = process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
350
|
-
const priorAppId = process.env.AI_ASSISTANT_APP_ID;
|
|
351
|
-
try {
|
|
352
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
353
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
354
|
-
delete process.env.AI_ASSISTANT_APP_ID;
|
|
355
|
-
const display = buildDisplayTable([
|
|
356
|
-
{ day_utc: '2026-02-25T00:00:00.000Z', completed_count: 4 }
|
|
357
|
-
]);
|
|
358
|
-
const markdown = formatDisplayTableMarkdown(display);
|
|
359
|
-
assert(
|
|
360
|
-
markdown.includes('02/25/2026'),
|
|
361
|
-
'Expected default date format to render dates as MM/DD/YYYY'
|
|
362
|
-
);
|
|
363
|
-
assert(
|
|
364
|
-
!markdown.includes('2026-02-25'),
|
|
365
|
-
'Expected default date format to avoid YYYY-MM-DD display dates'
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
finally {
|
|
369
|
-
if (priorInline === undefined) {
|
|
370
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
371
|
-
}
|
|
372
|
-
else {
|
|
373
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = priorInline;
|
|
374
|
-
}
|
|
375
|
-
if (priorFile === undefined) {
|
|
376
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = priorFile;
|
|
380
|
-
}
|
|
381
|
-
if (priorAppId === undefined) {
|
|
382
|
-
delete process.env.AI_ASSISTANT_APP_ID;
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
process.env.AI_ASSISTANT_APP_ID = priorAppId;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function testDeriveAssistantCommandExecutionStatus() {
|
|
391
|
-
assert(
|
|
392
|
-
deriveAssistantCommandExecutionStatus('/bin/bash -lc "rg --files /var/app/current"') === 'Searching files',
|
|
393
|
-
'Expected rg command to map to Searching files'
|
|
394
|
-
);
|
|
395
|
-
assert(
|
|
396
|
-
deriveAssistantCommandExecutionStatus('/bin/bash -lc "find /var/app/current -type f"') === 'Finding files',
|
|
397
|
-
'Expected find command to map to Finding files'
|
|
398
|
-
);
|
|
399
|
-
assert(
|
|
400
|
-
deriveAssistantCommandExecutionStatus('/bin/bash -lc "sed -n \'1,120p\' /var/app/current/file.ts"') === 'Opening files',
|
|
401
|
-
'Expected sed command to map to Opening files'
|
|
402
|
-
);
|
|
403
|
-
assert(
|
|
404
|
-
deriveAssistantCommandExecutionStatus('/bin/bash -lc "ls -la /var/app/current"') === 'Scanning files',
|
|
405
|
-
'Expected ls command to map to Scanning files'
|
|
406
|
-
);
|
|
407
|
-
assert(
|
|
408
|
-
deriveAssistantCommandExecutionStatus('/bin/bash -lc "echo hello"') === 'Running command',
|
|
409
|
-
'Expected unknown command to map to Running command'
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function testResolveCodexThoughtLevelPolicy() {
|
|
414
|
-
const keys = [
|
|
415
|
-
'AI_ASSISTANT_CODEX_THOUGHT_LEVEL',
|
|
416
|
-
'AI_TERMINAL_CODEX_THOUGHT_LEVEL',
|
|
417
|
-
'AI_DASHBOARD_CODEX_THOUGHT_LEVEL'
|
|
418
|
-
] as const;
|
|
419
|
-
const previous = keys.map((key) => [key, process.env[key]] as const);
|
|
420
|
-
try {
|
|
421
|
-
keys.forEach((key) => {
|
|
422
|
-
delete process.env[key];
|
|
423
|
-
});
|
|
424
|
-
const defaultNonBugLevel = resolveCodexThoughtLevel({
|
|
425
|
-
message: 'Explain what the deploy instance dashboard does',
|
|
426
|
-
requestType: 'feature_info'
|
|
427
|
-
});
|
|
428
|
-
assert(defaultNonBugLevel === 'low', 'Expected simple non-bug requests to default to low thought level');
|
|
429
|
-
|
|
430
|
-
keys.forEach((key) => {
|
|
431
|
-
process.env[key] = 'high';
|
|
432
|
-
});
|
|
433
|
-
const nonBugLevel = resolveCodexThoughtLevel({
|
|
434
|
-
message: 'Explain what the deploy instance dashboard does',
|
|
435
|
-
requestType: 'feature_info'
|
|
436
|
-
});
|
|
437
|
-
assert(nonBugLevel === 'medium', 'Expected non-bug requests to clamp to medium thought level');
|
|
438
|
-
|
|
439
|
-
const bugLevel = resolveCodexThoughtLevel({
|
|
440
|
-
message: "Why won't this save button work?",
|
|
441
|
-
requestType: 'bug_issue'
|
|
442
|
-
});
|
|
443
|
-
assert(bugLevel === 'high', 'Expected bug/issue requests to keep high thought level');
|
|
444
|
-
}
|
|
445
|
-
finally {
|
|
446
|
-
previous.forEach(([key, value]) => {
|
|
447
|
-
if (value === undefined) {
|
|
448
|
-
delete process.env[key];
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
process.env[key] = value;
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function testAssistantSystemPromptAllowsWorkspaceDebuggingAndFixes() {
|
|
458
|
-
const prompt = buildAssistantSystemPromptForTesting();
|
|
459
|
-
assert(
|
|
460
|
-
prompt.includes('workspace access to the current platform codebase'),
|
|
461
|
-
'Expected assistant system prompt to describe workspace access'
|
|
462
|
-
);
|
|
463
|
-
assert(
|
|
464
|
-
prompt.includes('make targeted code changes'),
|
|
465
|
-
'Expected assistant system prompt to allow targeted code changes for fixes'
|
|
466
|
-
);
|
|
467
|
-
assert(
|
|
468
|
-
prompt.includes('Use the provided read-only data bridge for database validation'),
|
|
469
|
-
'Expected assistant system prompt to direct database validation through the shared data bridge'
|
|
470
|
-
);
|
|
471
|
-
assert(
|
|
472
|
-
prompt.includes('Use the shared ResolveIO intelligence loop everywhere'),
|
|
473
|
-
'Expected assistant system prompt to include the shared ResolveIO intelligence loop'
|
|
474
|
-
);
|
|
475
|
-
assert(
|
|
476
|
-
prompt.includes('ResolveIO platform knowledge and repository evidence override generic model defaults'),
|
|
477
|
-
'Expected assistant system prompt to prioritize ResolveIO platform knowledge over generic defaults'
|
|
478
|
-
);
|
|
479
|
-
assert(
|
|
480
|
-
!prompt.includes('read-only access to the codebase'),
|
|
481
|
-
'Expected assistant system prompt to avoid the old read-only codebase restriction'
|
|
482
|
-
);
|
|
483
|
-
assert(
|
|
484
|
-
!prompt.includes('Do not modify files, run destructive commands, or access databases directly.'),
|
|
485
|
-
'Expected assistant system prompt to remove the old blanket modification restriction'
|
|
486
|
-
);
|
|
487
|
-
assert(
|
|
488
|
-
!prompt.includes('jobs and chemicals sent'),
|
|
489
|
-
'Expected shared prompt to avoid app-specific root/detail examples'
|
|
490
|
-
);
|
|
491
|
-
assert(
|
|
492
|
-
!prompt.includes('customer, well, or chemical name'),
|
|
493
|
-
'Expected shared prompt to avoid app-specific entity-name examples'
|
|
494
|
-
);
|
|
495
|
-
assert(
|
|
496
|
-
!prompt.includes('drivers, deliveries, routes, chemicals'),
|
|
497
|
-
'Expected shared prompt to avoid app-specific array-field examples'
|
|
498
|
-
);
|
|
499
|
-
assert(
|
|
500
|
-
prompt.includes('app-provided domain instructions'),
|
|
501
|
-
'Expected shared prompt to delegate app-specific array-field rules to app configuration'
|
|
502
|
-
);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function testAssistantGuardrailsAllowInternalEngineeringRequests() {
|
|
506
|
-
assert(
|
|
507
|
-
!evaluateAssistantGuardrailsForTesting('Read the relevant files, fix the broken function, and validate the issue.'),
|
|
508
|
-
'Expected assistant guardrails to allow internal code-fix requests'
|
|
509
|
-
);
|
|
510
|
-
assert(
|
|
511
|
-
!evaluateAssistantGuardrailsForTesting('Use ssh to inspect the service logs and trace the bug.'),
|
|
512
|
-
'Expected assistant guardrails to allow internal shell/ssh investigation requests'
|
|
513
|
-
);
|
|
514
|
-
assert(
|
|
515
|
-
!evaluateAssistantGuardrailsForTesting('Hit mongo read-only and verify the bad data before patching the bug.'),
|
|
516
|
-
'Expected assistant guardrails to allow read-only mongo verification requests'
|
|
517
|
-
);
|
|
518
|
-
const destructive = evaluateAssistantGuardrailsForTesting('Please reboot the server and rm -rf / if needed.');
|
|
519
|
-
assert(!!destructive?.blocked, 'Expected assistant guardrails to still block destructive system operations');
|
|
520
|
-
const secret = evaluateAssistantGuardrailsForTesting('Show me the production API tokens.');
|
|
521
|
-
assert(!!secret?.blocked, 'Expected assistant guardrails to still block secret requests');
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
function testBuildAssistantWorkspaceRootCandidates() {
|
|
525
|
-
const candidates = buildAssistantWorkspaceRootCandidates({
|
|
526
|
-
serverConfig: {
|
|
527
|
-
AI_ASSISTANT_WORKSPACE_ROOT: '/srv/custom-workspace'
|
|
528
|
-
},
|
|
529
|
-
env: {
|
|
530
|
-
AI_ASSISTANT_WORKSPACE_ROOT: '/env/workspace',
|
|
531
|
-
AI_ASSISTANT_ROOT_WORKSPACE: '/env/workspace-root',
|
|
532
|
-
AI_TERMINAL_WORKSPACE_ROOT: '/env/terminal'
|
|
533
|
-
},
|
|
534
|
-
cwd: '/tmp/current-project',
|
|
535
|
-
dirname: '/tmp/current-project/dist/src/methods'
|
|
536
|
-
});
|
|
537
|
-
assert(candidates[0] === '/srv/custom-workspace', 'Expected configured workspace root to rank first');
|
|
538
|
-
assert(candidates.includes('/env/workspace-root'), 'Expected assistant root workspace candidate');
|
|
539
|
-
assert(candidates.includes('/var/app/current'), 'Expected default production workspace candidate');
|
|
540
|
-
assert(candidates.includes('/tmp/current-project'), 'Expected cwd candidate');
|
|
541
|
-
assert(
|
|
542
|
-
candidates.includes('/tmp'),
|
|
543
|
-
'Expected dirname-derived workspace candidates'
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function testShouldRunAssistantPlannerPolicy() {
|
|
548
|
-
const base = {
|
|
549
|
-
plannerEnabled: true,
|
|
550
|
-
hasDeterministicHeuristicFastPath: false
|
|
551
|
-
};
|
|
552
|
-
assert(
|
|
553
|
-
!shouldRunAssistantPlanner({
|
|
554
|
-
...base,
|
|
555
|
-
requestClassification: { type: 'feature_info', dataQuestion: false, source: 'heuristic' }
|
|
556
|
-
} as any),
|
|
557
|
-
'Expected planner to skip non-data feature-info requests'
|
|
558
|
-
);
|
|
559
|
-
assert(
|
|
560
|
-
!shouldRunAssistantPlanner({
|
|
561
|
-
...base,
|
|
562
|
-
requestClassification: { type: 'bug_issue', dataQuestion: false, source: 'heuristic' }
|
|
563
|
-
} as any),
|
|
564
|
-
'Expected planner to skip non-data bug-issue requests'
|
|
565
|
-
);
|
|
566
|
-
assert(
|
|
567
|
-
shouldRunAssistantPlanner({
|
|
568
|
-
...base,
|
|
569
|
-
requestClassification: { type: 'data', dataQuestion: true, source: 'heuristic' }
|
|
570
|
-
} as any),
|
|
571
|
-
'Expected planner to run for data requests'
|
|
572
|
-
);
|
|
573
|
-
assert(
|
|
574
|
-
shouldRunAssistantPlanner({
|
|
575
|
-
...base,
|
|
576
|
-
requestClassification: { type: 'unknown', dataQuestion: false, source: 'heuristic' }
|
|
577
|
-
} as any),
|
|
578
|
-
'Expected planner to run for unknown intent requests'
|
|
579
|
-
);
|
|
580
|
-
assert(
|
|
581
|
-
!shouldRunAssistantPlanner({
|
|
582
|
-
...base,
|
|
583
|
-
requestClassification: { type: 'data', dataQuestion: true, source: 'heuristic' },
|
|
584
|
-
hasDeterministicHeuristicFastPath: true
|
|
585
|
-
} as any),
|
|
586
|
-
'Expected deterministic heuristic fast path to skip planner'
|
|
587
|
-
);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
function testAssistantAppHeuristicRegistryTermHintsAndDeterministicEnablement() {
|
|
591
|
-
const priorInline = process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
592
|
-
const priorFile = process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
593
|
-
const priorAppId = process.env.AI_ASSISTANT_APP_ID;
|
|
594
|
-
try {
|
|
595
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = '';
|
|
596
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = JSON.stringify({
|
|
597
|
-
apps: {
|
|
598
|
-
snf: {
|
|
599
|
-
enabled_deterministic_heuristics: ['schema_hours_user_time'],
|
|
600
|
-
collection_term_hints: [
|
|
601
|
-
{
|
|
602
|
-
terms: ['well', 'wells'],
|
|
603
|
-
collections: ['jobs', 'work-order-dynamics']
|
|
604
|
-
}
|
|
605
|
-
]
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
process.env.AI_ASSISTANT_APP_ID = 'snf';
|
|
610
|
-
assert(
|
|
611
|
-
isAssistantDeterministicHeuristicEnabledForApp('schema_hours_user_time', 'snf'),
|
|
612
|
-
'Expected app heuristics to enable schema_hours_user_time for snf'
|
|
613
|
-
);
|
|
614
|
-
assert(
|
|
615
|
-
!isAssistantDeterministicHeuristicEnabledForApp('schema_hours_user_time', 'resolveio'),
|
|
616
|
-
'Expected schema_hours_user_time to remain disabled for non-snf apps'
|
|
617
|
-
);
|
|
618
|
-
const hints = resolveAssistantAppCollectionHintCandidates('How many active wells are there?', 'snf');
|
|
619
|
-
assert(
|
|
620
|
-
hints.includes('jobs'),
|
|
621
|
-
'Expected app collection term hints to return jobs for wells in snf'
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
finally {
|
|
625
|
-
if (priorInline === undefined) {
|
|
626
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = priorInline;
|
|
630
|
-
}
|
|
631
|
-
if (priorFile === undefined) {
|
|
632
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
633
|
-
}
|
|
634
|
-
else {
|
|
635
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = priorFile;
|
|
636
|
-
}
|
|
637
|
-
if (priorAppId === undefined) {
|
|
638
|
-
delete process.env.AI_ASSISTANT_APP_ID;
|
|
639
|
-
}
|
|
640
|
-
else {
|
|
641
|
-
process.env.AI_ASSISTANT_APP_ID = priorAppId;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function testAssistantAppFieldSynonymsStayAppOwned() {
|
|
647
|
-
const baseProductTokens = expandAssistantFieldTokensForTesting(['product'], 'resolveio');
|
|
648
|
-
assert(
|
|
649
|
-
!baseProductTokens.includes('chemical') && !baseProductTokens.includes('blend'),
|
|
650
|
-
'Expected shared field synonyms to avoid app-domain product mappings'
|
|
651
|
-
);
|
|
652
|
-
const baseRevenueTokens = expandAssistantFieldTokensForTesting(['revenue'], 'resolveio');
|
|
653
|
-
assert(
|
|
654
|
-
!baseRevenueTokens.includes('paid_total') && !baseRevenueTokens.includes('grand_total'),
|
|
655
|
-
'Expected shared field synonyms to avoid app-domain revenue total mappings'
|
|
656
|
-
);
|
|
657
|
-
|
|
658
|
-
const priorInline = process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
659
|
-
const priorFile = process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
660
|
-
try {
|
|
661
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = '';
|
|
662
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = JSON.stringify({
|
|
663
|
-
apps: {
|
|
664
|
-
snf: {
|
|
665
|
-
field_synonyms: {
|
|
666
|
-
product: ['chemical', 'blend'],
|
|
667
|
-
revenue: ['paid_total', 'grand_total', 'billing']
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
});
|
|
672
|
-
const snfProductTokens = expandAssistantFieldTokensForTesting(['product'], 'snf');
|
|
673
|
-
assert(
|
|
674
|
-
snfProductTokens.includes('chemical') && snfProductTokens.includes('blend'),
|
|
675
|
-
'Expected SNF field synonyms to own product-to-chemical/blend mappings'
|
|
676
|
-
);
|
|
677
|
-
const snfRevenueTokens = expandAssistantFieldTokensForTesting(['revenue'], 'snf');
|
|
678
|
-
assert(
|
|
679
|
-
snfRevenueTokens.includes('paid_total') && snfRevenueTokens.includes('grand_total'),
|
|
680
|
-
'Expected SNF field synonyms to own revenue total mappings'
|
|
681
|
-
);
|
|
682
|
-
}
|
|
683
|
-
finally {
|
|
684
|
-
if (priorInline === undefined) {
|
|
685
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
686
|
-
}
|
|
687
|
-
else {
|
|
688
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = priorInline;
|
|
689
|
-
}
|
|
690
|
-
if (priorFile === undefined) {
|
|
691
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
692
|
-
}
|
|
693
|
-
else {
|
|
694
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = priorFile;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
function testAssistantAppHeuristicRegistryLoadsRelativeFileFromParityServerDir() {
|
|
700
|
-
const fs = require('fs');
|
|
701
|
-
const os = require('os');
|
|
702
|
-
const path = require('path');
|
|
703
|
-
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-assistant-heuristics-'));
|
|
704
|
-
const serverDir = path.join(tmpRoot, 'server');
|
|
705
|
-
const heuristicsDir = path.join(serverDir, 'src', 'ai');
|
|
706
|
-
const heuristicsPath = path.join(heuristicsDir, 'assistant-heuristics.json');
|
|
707
|
-
const semanticPath = path.join(heuristicsDir, 'assistant-semantic-manifest.json');
|
|
708
|
-
const priorInline = process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
709
|
-
const priorFile = process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
710
|
-
const priorAppId = process.env.AI_ASSISTANT_APP_ID;
|
|
711
|
-
const priorParityServerDir = process.env.AI_ASSISTANT_PARITY_SERVER_DIR;
|
|
712
|
-
const priorWorkspaceRoot = process.env.AI_ASSISTANT_WORKSPACE_ROOT;
|
|
713
|
-
try {
|
|
714
|
-
fs.mkdirSync(heuristicsDir, { recursive: true });
|
|
715
|
-
fs.writeFileSync(heuristicsPath, JSON.stringify({
|
|
716
|
-
apps: {
|
|
717
|
-
parityapp: {
|
|
718
|
-
collection_term_hints: [
|
|
719
|
-
{
|
|
720
|
-
terms: ['widgetfoo'],
|
|
721
|
-
collections: ['dashboard-widgets']
|
|
722
|
-
}
|
|
723
|
-
],
|
|
724
|
-
enabled_deterministic_heuristics: ['app_data_intent'],
|
|
725
|
-
data_intents: [
|
|
726
|
-
{
|
|
727
|
-
id: 'parity_widget_inventory',
|
|
728
|
-
terms: ['legacy widget trigger phrase'],
|
|
729
|
-
collection: 'dashboard-widgets',
|
|
730
|
-
permission_view: '/dashboard/widget-list',
|
|
731
|
-
date_field: 'date',
|
|
732
|
-
quantity_field: 'quantity'
|
|
733
|
-
}
|
|
734
|
-
]
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
}));
|
|
738
|
-
fs.writeFileSync(semanticPath, JSON.stringify({
|
|
739
|
-
app_id: 'parityapp',
|
|
740
|
-
manifest_id: 'parity-semantic-test',
|
|
741
|
-
semantic_app_data: true,
|
|
742
|
-
semantic_data_intents: [
|
|
743
|
-
{
|
|
744
|
-
id: 'parity_widget_inventory',
|
|
745
|
-
collection: 'dashboard-widgets',
|
|
746
|
-
domain: 'widget_inventory',
|
|
747
|
-
name: 'Widget inventory',
|
|
748
|
-
description: 'Answers widget stock and on-hand quantities grouped by location or owner.',
|
|
749
|
-
source_family: 'widget inventory snapshots',
|
|
750
|
-
workflow_kind: 'inventory_summary',
|
|
751
|
-
permission_view: '/dashboard/widget-list',
|
|
752
|
-
date_field: 'date',
|
|
753
|
-
measures: ['quantity'],
|
|
754
|
-
entity_types: ['widget', 'location', 'owner'],
|
|
755
|
-
semantic_only: true
|
|
756
|
-
}
|
|
757
|
-
]
|
|
758
|
-
}));
|
|
759
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
760
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = 'src/ai/assistant-heuristics.json';
|
|
761
|
-
process.env.AI_ASSISTANT_APP_ID = 'parityapp';
|
|
762
|
-
process.env.AI_ASSISTANT_PARITY_SERVER_DIR = serverDir;
|
|
763
|
-
process.env.AI_ASSISTANT_WORKSPACE_ROOT = tmpRoot;
|
|
764
|
-
const hints = resolveAssistantAppCollectionHintCandidates('Show me widgetfoo usage', 'parityapp');
|
|
765
|
-
assert(
|
|
766
|
-
hints.includes('dashboard-widgets'),
|
|
767
|
-
'Expected relative app heuristics file to resolve from AI_ASSISTANT_PARITY_SERVER_DIR'
|
|
768
|
-
);
|
|
769
|
-
const classifierMessages = buildAssistantDataRequestClassifierMessagesForTesting({
|
|
770
|
-
message: 'How much widget stock is on hand by location?',
|
|
771
|
-
appId: 'parityapp',
|
|
772
|
-
collectionNames: ['dashboard-widgets']
|
|
773
|
-
});
|
|
774
|
-
const classifierText = JSON.stringify(classifierMessages);
|
|
775
|
-
assert(
|
|
776
|
-
classifierText.includes('Answers widget stock and on-hand quantities grouped by location or owner.'),
|
|
777
|
-
'Expected companion semantic manifest to overlay the classifier candidate description'
|
|
778
|
-
);
|
|
779
|
-
assert(
|
|
780
|
-
classifierText.includes('semantic_only') && !classifierText.includes('legacy widget trigger phrase'),
|
|
781
|
-
'Expected semantic-only data intent to avoid exposing legacy trigger terms to the classifier'
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
finally {
|
|
785
|
-
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
786
|
-
if (priorInline === undefined) {
|
|
787
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
788
|
-
}
|
|
789
|
-
else {
|
|
790
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = priorInline;
|
|
791
|
-
}
|
|
792
|
-
if (priorFile === undefined) {
|
|
793
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
794
|
-
}
|
|
795
|
-
else {
|
|
796
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = priorFile;
|
|
797
|
-
}
|
|
798
|
-
if (priorAppId === undefined) {
|
|
799
|
-
delete process.env.AI_ASSISTANT_APP_ID;
|
|
800
|
-
}
|
|
801
|
-
else {
|
|
802
|
-
process.env.AI_ASSISTANT_APP_ID = priorAppId;
|
|
803
|
-
}
|
|
804
|
-
if (priorParityServerDir === undefined) {
|
|
805
|
-
delete process.env.AI_ASSISTANT_PARITY_SERVER_DIR;
|
|
806
|
-
}
|
|
807
|
-
else {
|
|
808
|
-
process.env.AI_ASSISTANT_PARITY_SERVER_DIR = priorParityServerDir;
|
|
809
|
-
}
|
|
810
|
-
if (priorWorkspaceRoot === undefined) {
|
|
811
|
-
delete process.env.AI_ASSISTANT_WORKSPACE_ROOT;
|
|
812
|
-
}
|
|
813
|
-
else {
|
|
814
|
-
process.env.AI_ASSISTANT_WORKSPACE_ROOT = priorWorkspaceRoot;
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
function testAssistantCoreHeuristicsStayPlatformScoped() {
|
|
820
|
-
const hints = resolveAssistantAppCollectionHintCandidates(
|
|
821
|
-
'The report builder collapse-table data table in the navbar module is broken and users cannot login because sessions keep expiring.',
|
|
822
|
-
'resolveio'
|
|
823
|
-
);
|
|
824
|
-
assert(
|
|
825
|
-
hints.includes('reports') || hints.includes('report-templates') || hints.includes('report-layouts'),
|
|
826
|
-
'Expected core heuristics to map report-builder/widget language to report collections'
|
|
827
|
-
);
|
|
828
|
-
assert(
|
|
829
|
-
hints.includes('dashboard-configs') || hints.includes('homepage-configs') || hints.includes('roles'),
|
|
830
|
-
'Expected core heuristics to map navbar/module language to shared nav/system collections'
|
|
831
|
-
);
|
|
832
|
-
assert(
|
|
833
|
-
hints.includes('users') || hints.includes('client-users') || hints.includes('billing-logged-in-users'),
|
|
834
|
-
'Expected core heuristics to map login/session language to user/auth collections'
|
|
835
|
-
);
|
|
836
|
-
assert(
|
|
837
|
-
!hints.includes('support-tickets'),
|
|
838
|
-
'Expected core heuristics to avoid app-domain support ticket hints'
|
|
839
|
-
);
|
|
840
|
-
assert(
|
|
841
|
-
!hints.includes('invoices'),
|
|
842
|
-
'Expected core heuristics to avoid app-domain invoice hints'
|
|
843
|
-
);
|
|
844
|
-
assert(
|
|
845
|
-
!hints.includes('work-order-dynamics') && !hints.includes('chemical-blends'),
|
|
846
|
-
'Expected core heuristics to avoid app-domain work-order and blend hints'
|
|
847
|
-
);
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
function testAssistantAppHeuristicsCanOwnDomainCollectionHints() {
|
|
851
|
-
const priorInline = process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
852
|
-
const priorFile = process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
853
|
-
try {
|
|
854
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = '';
|
|
855
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = JSON.stringify({
|
|
856
|
-
apps: {
|
|
857
|
-
snf: {
|
|
858
|
-
collection_term_hints: [
|
|
859
|
-
{
|
|
860
|
-
terms: ['invoice', 'invoices'],
|
|
861
|
-
collections: ['invoices']
|
|
862
|
-
},
|
|
863
|
-
{
|
|
864
|
-
terms: ['work order', 'work orders'],
|
|
865
|
-
collections: ['work-order-dynamics']
|
|
866
|
-
},
|
|
867
|
-
{
|
|
868
|
-
terms: ['blend', 'blending'],
|
|
869
|
-
collections: ['chemical-blends']
|
|
870
|
-
}
|
|
871
|
-
]
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
});
|
|
875
|
-
const hints = resolveAssistantAppCollectionHintCandidates(
|
|
876
|
-
'Show invoices, work orders, and blend throughput.',
|
|
877
|
-
'snf'
|
|
878
|
-
);
|
|
879
|
-
assert(
|
|
880
|
-
hints.includes('invoices'),
|
|
881
|
-
'Expected app heuristics to own invoice collection hints'
|
|
882
|
-
);
|
|
883
|
-
assert(
|
|
884
|
-
hints.includes('work-order-dynamics'),
|
|
885
|
-
'Expected app heuristics to own work-order collection hints'
|
|
886
|
-
);
|
|
887
|
-
assert(
|
|
888
|
-
hints.includes('chemical-blends'),
|
|
889
|
-
'Expected app heuristics to own blend collection hints'
|
|
890
|
-
);
|
|
891
|
-
}
|
|
892
|
-
finally {
|
|
893
|
-
if (priorInline === undefined) {
|
|
894
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
895
|
-
}
|
|
896
|
-
else {
|
|
897
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = priorInline;
|
|
898
|
-
}
|
|
899
|
-
if (priorFile === undefined) {
|
|
900
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
901
|
-
}
|
|
902
|
-
else {
|
|
903
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = priorFile;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
function testAssistantClassifierWorkflowArbitrationPrefersExplicitWorkOrders() {
|
|
909
|
-
const workOrderIntent = {
|
|
910
|
-
id: 'snf_job_chemical_shipments',
|
|
911
|
-
collection: 'work-order-dynamics',
|
|
912
|
-
permissionView: '/work-order-dynamic/list',
|
|
913
|
-
terms: ['work orders', 'work order chemicals', 'shipped chemicals'],
|
|
914
|
-
quantityField: 'chemicals.shipped.quantity',
|
|
915
|
-
filterFields: [{ id: 'status', terms: ['status', 'open'], fields: ['status'] }]
|
|
916
|
-
} as any;
|
|
917
|
-
const plannedComparisonIntent = {
|
|
918
|
-
id: 'snf_planned_vs_sent_chemicals',
|
|
919
|
-
collection: 'jobs',
|
|
920
|
-
permissionView: '/customer-info/jobs',
|
|
921
|
-
terms: ['planned vs shipped', 'planned versus sent'],
|
|
922
|
-
quantityField: 'planned_chemicals.quantity',
|
|
923
|
-
metricField: 'shipped_quantity',
|
|
924
|
-
dimensions: [{ id: 'chemical', terms: ['chemical', 'chemicals'] }],
|
|
925
|
-
filterFields: [{ id: 'chemical', terms: ['chemical', 'chemicals'], fields: ['planned_chemicals.chemical'] }]
|
|
926
|
-
} as any;
|
|
927
|
-
const candidates = [
|
|
928
|
-
{ intent: workOrderIntent, score: 9 },
|
|
929
|
-
{ intent: plannedComparisonIntent, score: 0 }
|
|
930
|
-
] as any;
|
|
931
|
-
const explicitWorkOrders = adjustAssistantClassifiedAppDataIntentForTesting(
|
|
932
|
-
plannedComparisonIntent,
|
|
933
|
-
candidates,
|
|
934
|
-
{ intent_id: 'snf_planned_vs_sent_chemicals' },
|
|
935
|
-
'Show open work orders with planned or shipped chemicals'
|
|
936
|
-
);
|
|
937
|
-
assert(
|
|
938
|
-
explicitWorkOrders?.id === 'snf_job_chemical_shipments',
|
|
939
|
-
'Expected explicit work-order requests to keep the work-order workflow even when the text mentions planned chemicals'
|
|
940
|
-
);
|
|
941
|
-
const comparison = adjustAssistantClassifiedAppDataIntentForTesting(
|
|
942
|
-
workOrderIntent,
|
|
943
|
-
candidates,
|
|
944
|
-
{ intent_id: 'snf_job_chemical_shipments' },
|
|
945
|
-
'Compare planned vs shipped chemicals by work order'
|
|
946
|
-
);
|
|
947
|
-
assert(
|
|
948
|
-
comparison?.id === 'snf_planned_vs_sent_chemicals',
|
|
949
|
-
'Expected comparison wording to continue using the planned-vs-shipped workflow'
|
|
950
|
-
);
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
function testAssistantAppDataIntentsResolveSnfDomainRequests() {
|
|
954
|
-
const priorInline = process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
955
|
-
const priorFile = process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
956
|
-
try {
|
|
957
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = '';
|
|
958
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = JSON.stringify({
|
|
959
|
-
apps: {
|
|
960
|
-
snf: {
|
|
961
|
-
enabled_deterministic_heuristics: ['app_data_intent'],
|
|
962
|
-
data_intents: [
|
|
963
|
-
{
|
|
964
|
-
id: 'snf_ats_driver_pay_summary',
|
|
965
|
-
terms: [
|
|
966
|
-
'ats pay',
|
|
967
|
-
'ats pays',
|
|
968
|
-
'ats settlement',
|
|
969
|
-
'ats settlements',
|
|
970
|
-
'driver pay',
|
|
971
|
-
'driver settlement',
|
|
972
|
-
'driver settlements',
|
|
973
|
-
'settlement payout',
|
|
974
|
-
'settlement pay'
|
|
975
|
-
],
|
|
976
|
-
collection: 'ats-settlements',
|
|
977
|
-
permission_view: '/ats-settlement/list',
|
|
978
|
-
date_field: 'date_end',
|
|
979
|
-
metric_field: 'total_payout',
|
|
980
|
-
fallback_collections: ['ats-pays', 'wo-type-ats-pays', 'users'],
|
|
981
|
-
acknowledgement: 'I understand you want an ATS driver pay breakdown for the requested date range. Let me summarize matching settlements.',
|
|
982
|
-
progress: ['Checking ATS settlements for the requested driver and date range', 'Summarizing payout totals'],
|
|
983
|
-
assumptions: ['ATS pay means driver settlement payouts from ats-settlements, not invoice billing'],
|
|
984
|
-
dimensions: {
|
|
985
|
-
month: {
|
|
986
|
-
type: 'time',
|
|
987
|
-
granularity: 'month',
|
|
988
|
-
terms: ['month', 'months', 'monthly'],
|
|
989
|
-
group_id: {
|
|
990
|
-
month: {
|
|
991
|
-
$dateToString: {
|
|
992
|
-
format: '%Y-%m',
|
|
993
|
-
date: '$date_end'
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
|
-
},
|
|
997
|
-
project: {
|
|
998
|
-
month: '$_id.month'
|
|
999
|
-
},
|
|
1000
|
-
sort: {
|
|
1001
|
-
month: -1
|
|
1002
|
-
}
|
|
1003
|
-
},
|
|
1004
|
-
driver: {
|
|
1005
|
-
terms: ['driver', 'drivers', 'user', 'employee', 'name'],
|
|
1006
|
-
group_id: {
|
|
1007
|
-
id_user: '$id_user',
|
|
1008
|
-
user: '$user'
|
|
1009
|
-
},
|
|
1010
|
-
project: {
|
|
1011
|
-
id_user: '$_id.id_user',
|
|
1012
|
-
user: '$_id.user'
|
|
1013
|
-
},
|
|
1014
|
-
sort: {
|
|
1015
|
-
total_payout: -1,
|
|
1016
|
-
user: 1
|
|
1017
|
-
}
|
|
1018
|
-
},
|
|
1019
|
-
status: {
|
|
1020
|
-
terms: ['status', 'pending approval', 'approved', 'published'],
|
|
1021
|
-
group_id: {
|
|
1022
|
-
status: '$status'
|
|
1023
|
-
},
|
|
1024
|
-
project: {
|
|
1025
|
-
status: '$_id.status'
|
|
1026
|
-
},
|
|
1027
|
-
sort: {
|
|
1028
|
-
total_payout: -1,
|
|
1029
|
-
status: 1
|
|
1030
|
-
}
|
|
1031
|
-
}
|
|
1032
|
-
},
|
|
1033
|
-
filter_fields: {
|
|
1034
|
-
driver: {
|
|
1035
|
-
terms: ['driver', 'drivers', 'user', 'employee', 'name'],
|
|
1036
|
-
fields: ['user', 'id_user']
|
|
1037
|
-
},
|
|
1038
|
-
status: {
|
|
1039
|
-
terms: ['status', 'pending approval', 'approved', 'published'],
|
|
1040
|
-
field: 'status'
|
|
1041
|
-
}
|
|
1042
|
-
},
|
|
1043
|
-
pipeline: [
|
|
1044
|
-
{
|
|
1045
|
-
$match: {
|
|
1046
|
-
date_start: {
|
|
1047
|
-
$gte: '{{date_start}}'
|
|
1048
|
-
},
|
|
1049
|
-
date_end: {
|
|
1050
|
-
$lte: '{{date_end}}'
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
},
|
|
1054
|
-
{
|
|
1055
|
-
$group: {
|
|
1056
|
-
_id: {
|
|
1057
|
-
id_user: '$id_user',
|
|
1058
|
-
user: '$user',
|
|
1059
|
-
status: '$status'
|
|
1060
|
-
},
|
|
1061
|
-
settlement_count: { $sum: 1 },
|
|
1062
|
-
load_count: { $sum: { $size: { $ifNull: ['$loads', []] } } },
|
|
1063
|
-
load_payout: { $sum: '$load_payout' },
|
|
1064
|
-
standby_pay: { $sum: '$standby_pay' },
|
|
1065
|
-
total_payout: { $sum: '$total_payout' },
|
|
1066
|
-
first_date_start: { $min: '$date_start' },
|
|
1067
|
-
last_date_end: { $max: '$date_end' },
|
|
1068
|
-
last_date_paid: { $max: '$date_paid' }
|
|
1069
|
-
}
|
|
1070
|
-
},
|
|
1071
|
-
{
|
|
1072
|
-
$project: {
|
|
1073
|
-
_id: 0,
|
|
1074
|
-
id_user: '$_id.id_user',
|
|
1075
|
-
user: '$_id.user',
|
|
1076
|
-
status: '$_id.status',
|
|
1077
|
-
settlement_count: 1,
|
|
1078
|
-
load_count: 1,
|
|
1079
|
-
load_payout: 1,
|
|
1080
|
-
standby_pay: 1,
|
|
1081
|
-
total_payout: 1,
|
|
1082
|
-
first_date_start: 1,
|
|
1083
|
-
last_date_end: 1,
|
|
1084
|
-
last_date_paid: 1
|
|
1085
|
-
}
|
|
1086
|
-
},
|
|
1087
|
-
{ $sort: { last_date_end: -1, total_payout: -1, user: 1, status: 1 } }
|
|
1088
|
-
]
|
|
1089
|
-
},
|
|
1090
|
-
{
|
|
1091
|
-
id: 'snf_work_order_summary',
|
|
1092
|
-
terms: [
|
|
1093
|
-
'work order summary',
|
|
1094
|
-
'work order breakdown',
|
|
1095
|
-
'break down work orders',
|
|
1096
|
-
'work orders',
|
|
1097
|
-
'work order'
|
|
1098
|
-
],
|
|
1099
|
-
collection: 'work-order-dynamics',
|
|
1100
|
-
permission_view: '/work-order-dynamic/list',
|
|
1101
|
-
date_field: 'date_completed',
|
|
1102
|
-
acknowledgement: 'I understand you want a work order breakdown for the requested date range. Let me check matching work orders.',
|
|
1103
|
-
progress: ['Checking work orders for the requested date range', 'Grouping matching work orders'],
|
|
1104
|
-
dimensions: {
|
|
1105
|
-
work_order: {
|
|
1106
|
-
terms: ['work order', 'work orders'],
|
|
1107
|
-
group_id: {
|
|
1108
|
-
id_work_order: '$_id',
|
|
1109
|
-
wo_string: '$wo_string',
|
|
1110
|
-
division: '$division',
|
|
1111
|
-
status: '$status',
|
|
1112
|
-
date_completed: '$date_completed'
|
|
1113
|
-
},
|
|
1114
|
-
project: {
|
|
1115
|
-
id_work_order: '$_id.id_work_order',
|
|
1116
|
-
wo_string: '$_id.wo_string',
|
|
1117
|
-
division: '$_id.division',
|
|
1118
|
-
status: '$_id.status',
|
|
1119
|
-
date_completed: '$_id.date_completed'
|
|
1120
|
-
},
|
|
1121
|
-
sort: {
|
|
1122
|
-
date_completed: -1,
|
|
1123
|
-
wo_string: -1
|
|
1124
|
-
}
|
|
1125
|
-
},
|
|
1126
|
-
division: {
|
|
1127
|
-
terms: ['division', 'divisions'],
|
|
1128
|
-
group_id: {
|
|
1129
|
-
division: '$division'
|
|
1130
|
-
},
|
|
1131
|
-
project: {
|
|
1132
|
-
division: '$_id.division'
|
|
1133
|
-
},
|
|
1134
|
-
sort: {
|
|
1135
|
-
work_order_count: -1,
|
|
1136
|
-
division: 1
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
},
|
|
1140
|
-
filter_fields: {
|
|
1141
|
-
division: {
|
|
1142
|
-
terms: ['division', 'divisions'],
|
|
1143
|
-
field: 'division'
|
|
1144
|
-
},
|
|
1145
|
-
status: {
|
|
1146
|
-
terms: ['status', 'open', 'completed', 'reviewed', 'closed'],
|
|
1147
|
-
field: 'status'
|
|
1148
|
-
}
|
|
1149
|
-
},
|
|
1150
|
-
pipeline: [
|
|
1151
|
-
{
|
|
1152
|
-
$match: {
|
|
1153
|
-
date_completed: {
|
|
1154
|
-
$gte: '{{date_start}}',
|
|
1155
|
-
$lte: '{{date_end}}'
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
},
|
|
1159
|
-
{ $match: { status: { $in: ['Completed', 'Reviewed', 'Closed'] } } },
|
|
1160
|
-
{
|
|
1161
|
-
$group: {
|
|
1162
|
-
_id: {
|
|
1163
|
-
id_work_order: '$_id',
|
|
1164
|
-
wo_string: '$wo_string',
|
|
1165
|
-
division: '$division',
|
|
1166
|
-
status: '$status',
|
|
1167
|
-
date_completed: '$date_completed'
|
|
1168
|
-
},
|
|
1169
|
-
work_order_count: { $sum: 1 }
|
|
1170
|
-
}
|
|
1171
|
-
},
|
|
1172
|
-
{
|
|
1173
|
-
$project: {
|
|
1174
|
-
_id: 0,
|
|
1175
|
-
id_work_order: '$_id.id_work_order',
|
|
1176
|
-
wo_string: '$_id.wo_string',
|
|
1177
|
-
division: '$_id.division',
|
|
1178
|
-
status: '$_id.status',
|
|
1179
|
-
date_completed: '$_id.date_completed',
|
|
1180
|
-
work_order_count: 1
|
|
1181
|
-
}
|
|
1182
|
-
},
|
|
1183
|
-
{ $sort: { date_completed: -1, wo_string: -1 } }
|
|
1184
|
-
]
|
|
1185
|
-
},
|
|
1186
|
-
{
|
|
1187
|
-
id: 'snf_job_chemical_shipments',
|
|
1188
|
-
terms: [
|
|
1189
|
-
'amount of chemicals sent',
|
|
1190
|
-
'chemicals sent',
|
|
1191
|
-
'chemicals were sent',
|
|
1192
|
-
'what chemicals were sent',
|
|
1193
|
-
'shipped chemicals',
|
|
1194
|
-
'chemical shipments',
|
|
1195
|
-
'work order chemicals',
|
|
1196
|
-
'work order shipments'
|
|
1197
|
-
],
|
|
1198
|
-
collection: 'work-order-dynamics',
|
|
1199
|
-
permission_view: '/work-order-dynamic/list',
|
|
1200
|
-
date_field: 'date_completed',
|
|
1201
|
-
quantity_field: 'chemicals.shipped.quantity',
|
|
1202
|
-
fallback_collections: ['invoices', 'jobs'],
|
|
1203
|
-
acknowledgement: 'I understand you want a summary of chemicals sent to {{customer}} jobs for {{year}}. Let me check the matching work orders.',
|
|
1204
|
-
progress: ['Checking {{customer}} work orders for {{year}}', 'Summarizing shipped chemical quantities'],
|
|
1205
|
-
assumptions: ['Sent means shipped chemical quantity'],
|
|
1206
|
-
filter_fields: {
|
|
1207
|
-
customer: {
|
|
1208
|
-
terms: ['customer', 'customers', 'client', 'clients'],
|
|
1209
|
-
fields: ['customer', 'job.customer']
|
|
1210
|
-
},
|
|
1211
|
-
status: {
|
|
1212
|
-
terms: ['status', 'open', 'completed', 'reviewed', 'closed'],
|
|
1213
|
-
field: 'status'
|
|
1214
|
-
}
|
|
1215
|
-
},
|
|
1216
|
-
pipeline: [
|
|
1217
|
-
{
|
|
1218
|
-
$match: {
|
|
1219
|
-
$and: [
|
|
1220
|
-
{
|
|
1221
|
-
$or: [
|
|
1222
|
-
{ customer: { $regex: '{{customer}}', $options: 'i' } },
|
|
1223
|
-
{ 'job.customer': { $regex: '{{customer}}', $options: 'i' } }
|
|
1224
|
-
]
|
|
1225
|
-
},
|
|
1226
|
-
{
|
|
1227
|
-
date_completed: {
|
|
1228
|
-
$gte: '{{date_start}}',
|
|
1229
|
-
$lte: '{{date_end}}'
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
]
|
|
1233
|
-
}
|
|
1234
|
-
},
|
|
1235
|
-
{ $match: { status: { $in: ['Completed', 'Reviewed', 'Closed'] } } },
|
|
1236
|
-
{ $unwind: '$chemicals' },
|
|
1237
|
-
{ $match: { 'chemicals.shipped.quantity': { $gt: 0 } } },
|
|
1238
|
-
{
|
|
1239
|
-
$group: {
|
|
1240
|
-
_id: {
|
|
1241
|
-
chemical: '$chemicals.chemical'
|
|
1242
|
-
},
|
|
1243
|
-
qty_sent: { $sum: '$chemicals.shipped.quantity' }
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
]
|
|
1247
|
-
},
|
|
1248
|
-
{
|
|
1249
|
-
id: 'snf_invoice_chemical_quantities',
|
|
1250
|
-
terms: ['invoice', 'invoices', 'billed chemicals', 'chemicals billed', 'how many chemicals', 'amount charged'],
|
|
1251
|
-
collection: 'invoices',
|
|
1252
|
-
permission_view: '/invoice/list',
|
|
1253
|
-
date_field: 'date_created',
|
|
1254
|
-
quantity_field: 'items_chemicals.quantity',
|
|
1255
|
-
metric_field: 'items_chemicals.price_total',
|
|
1256
|
-
dimensions: {
|
|
1257
|
-
chemical: {
|
|
1258
|
-
terms: ['chemical', 'chemicals', 'product', 'products'],
|
|
1259
|
-
group_id: {
|
|
1260
|
-
chemical: { $ifNull: ['$items_chemicals.chemical', 'Unknown'] }
|
|
1261
|
-
},
|
|
1262
|
-
project: {
|
|
1263
|
-
chemical: '$_id.chemical'
|
|
1264
|
-
}
|
|
1265
|
-
},
|
|
1266
|
-
customer: {
|
|
1267
|
-
terms: ['customer', 'customers'],
|
|
1268
|
-
group_id: {
|
|
1269
|
-
customer: { $ifNull: ['$customer', 'Unknown'] }
|
|
1270
|
-
},
|
|
1271
|
-
project: {
|
|
1272
|
-
customer: '$_id.customer'
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
},
|
|
1276
|
-
filter_fields: {
|
|
1277
|
-
chemical: {
|
|
1278
|
-
terms: ['chemical', 'chemicals', 'product', 'products'],
|
|
1279
|
-
field: 'items_chemicals.chemical'
|
|
1280
|
-
},
|
|
1281
|
-
customer: {
|
|
1282
|
-
terms: ['customer', 'customers'],
|
|
1283
|
-
fields: ['customer', 'job.customer']
|
|
1284
|
-
}
|
|
1285
|
-
}
|
|
1286
|
-
},
|
|
1287
|
-
{
|
|
1288
|
-
id: 'snf_chemical_activity_summary',
|
|
1289
|
-
terms: ['chemical activity', 'activity', 'summarize all the activity', 'nacl brine'],
|
|
1290
|
-
collection: 'chemical-transactions',
|
|
1291
|
-
permission_view: '/chemical/detail',
|
|
1292
|
-
date_field: 'date',
|
|
1293
|
-
quantity_field: 'quantity',
|
|
1294
|
-
metric_field: 'quantity_diff',
|
|
1295
|
-
filter_fields: {
|
|
1296
|
-
chemical: {
|
|
1297
|
-
terms: ['chemical', 'nacl brine'],
|
|
1298
|
-
field: 'chemical'
|
|
1299
|
-
}
|
|
1300
|
-
},
|
|
1301
|
-
pipeline: [
|
|
1302
|
-
{
|
|
1303
|
-
$match: {
|
|
1304
|
-
date: {
|
|
1305
|
-
$gte: '{{date_start}}',
|
|
1306
|
-
$lte: '{{date_end}}'
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
},
|
|
1310
|
-
{
|
|
1311
|
-
$group: {
|
|
1312
|
-
_id: {
|
|
1313
|
-
chemical: { $ifNull: ['$chemical', 'Unknown'] },
|
|
1314
|
-
activity_type: { $ifNull: ['$activity_type', 'Unknown'] },
|
|
1315
|
-
type: { $ifNull: ['$type', 'Unknown'] }
|
|
1316
|
-
},
|
|
1317
|
-
activity_count: { $sum: 1 },
|
|
1318
|
-
quantity: { $sum: '$quantity' },
|
|
1319
|
-
quantity_diff: { $sum: '$quantity_diff' }
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
]
|
|
1323
|
-
},
|
|
1324
|
-
{
|
|
1325
|
-
id: 'snf_chemical_blends_summary',
|
|
1326
|
-
terms: ['chemical blends summary', 'chemical blends'],
|
|
1327
|
-
collection: 'chemical-blends',
|
|
1328
|
-
permission_view: '/chemical-blend/list',
|
|
1329
|
-
date_field: 'date',
|
|
1330
|
-
quantity_field: 'quantity',
|
|
1331
|
-
dimensions: {
|
|
1332
|
-
chemical: {
|
|
1333
|
-
terms: ['chemical', 'product'],
|
|
1334
|
-
group_id: {
|
|
1335
|
-
chemical: {
|
|
1336
|
-
$ifNull: ['$chemical', 'Unknown']
|
|
1337
|
-
}
|
|
1338
|
-
},
|
|
1339
|
-
project: {
|
|
1340
|
-
chemical: '$_id.chemical'
|
|
1341
|
-
},
|
|
1342
|
-
sort: {
|
|
1343
|
-
quantity: -1,
|
|
1344
|
-
chemical: 1
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
},
|
|
1348
|
-
pipeline: [
|
|
1349
|
-
{
|
|
1350
|
-
$match: {
|
|
1351
|
-
date: {
|
|
1352
|
-
$gte: '{{date_start}}',
|
|
1353
|
-
$lte: '{{date_end}}'
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
},
|
|
1357
|
-
{
|
|
1358
|
-
$group: {
|
|
1359
|
-
_id: null,
|
|
1360
|
-
blend_count: { $sum: 1 },
|
|
1361
|
-
quantity: { $sum: '$quantity' }
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
]
|
|
1365
|
-
},
|
|
1366
|
-
{
|
|
1367
|
-
id: 'snf_planned_job_chemicals',
|
|
1368
|
-
terms: ['planned chemicals', 'planned chemical', 'chemicals were planned', 'what chemicals were planned', 'planned chemicals by customer'],
|
|
1369
|
-
collection: 'jobs',
|
|
1370
|
-
permission_view: '/customer-info/jobs',
|
|
1371
|
-
date_field: 'date_job_created',
|
|
1372
|
-
quantity_field: 'planned_chemicals.quantity',
|
|
1373
|
-
dimensions: {
|
|
1374
|
-
chemical: {
|
|
1375
|
-
terms: ['chemical', 'chemicals', 'product', 'products'],
|
|
1376
|
-
group_id: {
|
|
1377
|
-
chemical: { $ifNull: ['$planned_chemicals.chemical', 'Unknown'] }
|
|
1378
|
-
},
|
|
1379
|
-
project: {
|
|
1380
|
-
chemical: '$_id.chemical'
|
|
1381
|
-
}
|
|
1382
|
-
},
|
|
1383
|
-
customer: {
|
|
1384
|
-
terms: ['customer', 'customers'],
|
|
1385
|
-
group_id: {
|
|
1386
|
-
customer: { $ifNull: ['$customer', 'Unknown'] }
|
|
1387
|
-
},
|
|
1388
|
-
project: {
|
|
1389
|
-
customer: '$_id.customer'
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
},
|
|
1393
|
-
filter_fields: {
|
|
1394
|
-
chemical: {
|
|
1395
|
-
terms: ['chemical', 'chemicals', 'product', 'products'],
|
|
1396
|
-
field: 'planned_chemicals.chemical'
|
|
1397
|
-
},
|
|
1398
|
-
customer: {
|
|
1399
|
-
terms: ['customer', 'customers'],
|
|
1400
|
-
fields: ['customer', 'jca.customer']
|
|
1401
|
-
},
|
|
1402
|
-
status: {
|
|
1403
|
-
terms: ['status', 'open', 'closed', 'completed'],
|
|
1404
|
-
field: 'status'
|
|
1405
|
-
},
|
|
1406
|
-
location: {
|
|
1407
|
-
terms: ['location', 'yard'],
|
|
1408
|
-
field: 'location'
|
|
1409
|
-
}
|
|
1410
|
-
},
|
|
1411
|
-
pipeline: [
|
|
1412
|
-
{
|
|
1413
|
-
$match: {
|
|
1414
|
-
date_job_created: {
|
|
1415
|
-
$gte: '{{date_start}}',
|
|
1416
|
-
$lte: '{{date_end}}'
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
},
|
|
1420
|
-
{ $unwind: '$planned_chemicals' },
|
|
1421
|
-
{ $match: {} },
|
|
1422
|
-
{
|
|
1423
|
-
$group: {
|
|
1424
|
-
_id: {
|
|
1425
|
-
chemical: '$planned_chemicals.chemical'
|
|
1426
|
-
},
|
|
1427
|
-
planned_quantity: { $sum: '$planned_chemicals.quantity' }
|
|
1428
|
-
}
|
|
1429
|
-
}
|
|
1430
|
-
]
|
|
1431
|
-
},
|
|
1432
|
-
{
|
|
1433
|
-
id: 'snf_chemical_inventory',
|
|
1434
|
-
terms: ['inventory', 'chemical inventory', 'current inventory'],
|
|
1435
|
-
collection: 'chemical-inventory-snapshots',
|
|
1436
|
-
permission_view: '/chemical-inventory-snapshot/list',
|
|
1437
|
-
date_field: 'date',
|
|
1438
|
-
quantity_field: 'quantity',
|
|
1439
|
-
assumptions: ['A named chemical or product in an inventory request is a chemical filter'],
|
|
1440
|
-
dimensions: {
|
|
1441
|
-
location: {
|
|
1442
|
-
terms: ['location', 'locations', 'yard'],
|
|
1443
|
-
group_id: {
|
|
1444
|
-
location: { $ifNull: ['$location', 'Unknown'] }
|
|
1445
|
-
},
|
|
1446
|
-
project: {
|
|
1447
|
-
location: '$_id.location'
|
|
1448
|
-
}
|
|
1449
|
-
},
|
|
1450
|
-
chemical: {
|
|
1451
|
-
terms: ['chemical', 'chemicals', 'product', 'products'],
|
|
1452
|
-
group_id: {
|
|
1453
|
-
chemical: { $ifNull: ['$chemical', 'Unknown'] }
|
|
1454
|
-
},
|
|
1455
|
-
project: {
|
|
1456
|
-
chemical: '$_id.chemical'
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
},
|
|
1460
|
-
filter_fields: {
|
|
1461
|
-
chemical: {
|
|
1462
|
-
terms: ['chemical', 'chemicals', 'product', 'products'],
|
|
1463
|
-
field: 'chemical'
|
|
1464
|
-
},
|
|
1465
|
-
location: {
|
|
1466
|
-
terms: ['location', 'locations', 'yard'],
|
|
1467
|
-
field: 'location'
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
]
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
});
|
|
1475
|
-
const collectionNames = ['jobs', 'work-order-dynamics', 'invoices', 'ats-settlements', 'ats-pays', 'wo-type-ats-pays', 'chemical-blends', 'chemical-transactions', 'chemical-inventory-snapshots'];
|
|
1476
|
-
const shipmentDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1477
|
-
'Summarize Devon Energy jobs and amount of chemicals sent to them for this year',
|
|
1478
|
-
['chemicals', 'jobs', 'work-order-dynamics'],
|
|
1479
|
-
collectionNames,
|
|
1480
|
-
'snf'
|
|
1481
|
-
);
|
|
1482
|
-
assert(shipmentDirective?.payload?.collection === 'work-order-dynamics', 'Expected shipped-chemical job request to resolve to work-order-dynamics');
|
|
1483
|
-
assert(String(shipmentDirective?.rawLine || '').includes('app-data-intent:snf_job_chemical_shipments'), 'Expected shipped-chemical request to use the SNF shipment recipe');
|
|
1484
|
-
assert(
|
|
1485
|
-
String(shipmentDirective?.metadata?.acknowledgementText || '').includes('I understand you want a summary of chemicals sent to Devon Energy jobs'),
|
|
1486
|
-
'Expected shipment recipe to create a custom acknowledgement'
|
|
1487
|
-
);
|
|
1488
|
-
assert(
|
|
1489
|
-
JSON.stringify(shipmentDirective?.payload?.pipeline || []).includes('chemicals.shipped.quantity'),
|
|
1490
|
-
'Expected shipment recipe to aggregate shipped chemical quantity'
|
|
1491
|
-
);
|
|
1492
|
-
assert(
|
|
1493
|
-
shipmentDirective?.payload?.options?.disableNoDataFallbacks === true,
|
|
1494
|
-
'Expected app-owned deterministic data intents to skip generic no-data aggregate fallbacks'
|
|
1495
|
-
);
|
|
1496
|
-
const atsPayDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1497
|
-
"Breakdown Fairbank, Michael's ats pay over the last year",
|
|
1498
|
-
['ats-settlements', 'ats-pays'],
|
|
1499
|
-
collectionNames,
|
|
1500
|
-
'snf'
|
|
1501
|
-
);
|
|
1502
|
-
const atsPayPipelineText = JSON.stringify(atsPayDirective?.payload?.pipeline || []);
|
|
1503
|
-
assert(
|
|
1504
|
-
atsPayDirective?.payload?.collection === 'ats-settlements',
|
|
1505
|
-
'Expected ATS pay request to resolve to ats-settlements instead of invoices'
|
|
1506
|
-
);
|
|
1507
|
-
assert(
|
|
1508
|
-
String(atsPayDirective?.rawLine || '').includes('app-data-intent:snf_ats_driver_pay_summary'),
|
|
1509
|
-
'Expected ATS pay request to use the SNF ATS driver pay recipe'
|
|
1510
|
-
);
|
|
1511
|
-
assert(
|
|
1512
|
-
atsPayPipelineText.includes('date_start') && atsPayPipelineText.includes('date_end') && atsPayPipelineText.includes('$gte') && atsPayPipelineText.includes('$lte'),
|
|
1513
|
-
'Expected over the last year to become an ATS settlement date window'
|
|
1514
|
-
);
|
|
1515
|
-
assert(
|
|
1516
|
-
atsPayPipelineText.includes('"user":{"$regex":"Fairbank.*Michael"') && !atsPayPipelineText.includes('ats pay') && !atsPayPipelineText.includes(' over'),
|
|
1517
|
-
'Expected Fairbank, Michael to become a punctuation-tolerant driver filter without ATS/date filler words'
|
|
1518
|
-
);
|
|
1519
|
-
assert(
|
|
1520
|
-
atsPayPipelineText.includes('total_payout') && atsPayPipelineText.includes('settlement_count') && !atsPayPipelineText.includes('customer_group'),
|
|
1521
|
-
'Expected ATS pay output to summarize settlement payout fields, not invoice customer groups'
|
|
1522
|
-
);
|
|
1523
|
-
const naturalShipmentDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1524
|
-
'What chemicals were sent to Devon Energy last month?',
|
|
1525
|
-
['chemicals', 'jobs', 'work-order-dynamics'],
|
|
1526
|
-
collectionNames,
|
|
1527
|
-
'snf'
|
|
1528
|
-
);
|
|
1529
|
-
assert(naturalShipmentDirective?.payload?.collection === 'work-order-dynamics', 'Expected natural shipped-chemical request to resolve to work-order-dynamics');
|
|
1530
|
-
assert(
|
|
1531
|
-
JSON.stringify(naturalShipmentDirective?.payload?.pipeline || []).includes('date_completed'),
|
|
1532
|
-
'Expected natural shipped-chemical request to include the configured date field'
|
|
1533
|
-
);
|
|
1534
|
-
assert(
|
|
1535
|
-
JSON.stringify(naturalShipmentDirective?.payload?.pipeline || []).includes('__assistant_sort_updatedAt'),
|
|
1536
|
-
'Expected app data aggregates without explicit sort to default to newest updatedAt/createdAt records'
|
|
1537
|
-
);
|
|
1538
|
-
const divisionWorkOrderDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1539
|
-
'Give me a breakdown of obsidian division work orders last few months',
|
|
1540
|
-
['work-order-dynamics'],
|
|
1541
|
-
collectionNames,
|
|
1542
|
-
'snf'
|
|
1543
|
-
);
|
|
1544
|
-
const divisionWorkOrderPipelineText = JSON.stringify(divisionWorkOrderDirective?.payload?.pipeline || []);
|
|
1545
|
-
assert(
|
|
1546
|
-
divisionWorkOrderDirective?.payload?.collection === 'work-order-dynamics',
|
|
1547
|
-
'Expected division work-order request to resolve to work-order-dynamics'
|
|
1548
|
-
);
|
|
1549
|
-
assert(
|
|
1550
|
-
String(divisionWorkOrderDirective?.rawLine || '').includes('app-data-intent:snf_work_order_summary'),
|
|
1551
|
-
'Expected plain work-order request to use the general work-order summary recipe'
|
|
1552
|
-
);
|
|
1553
|
-
assert(
|
|
1554
|
-
String(divisionWorkOrderDirective?.metadata?.customerText || '') === '',
|
|
1555
|
-
'Expected division filter text not to become customerText'
|
|
1556
|
-
);
|
|
1557
|
-
assert(
|
|
1558
|
-
divisionWorkOrderPipelineText.includes('date_completed') && divisionWorkOrderPipelineText.includes('$gte') && divisionWorkOrderPipelineText.includes('$lte'),
|
|
1559
|
-
'Expected last few months to become a configured date_completed window'
|
|
1560
|
-
);
|
|
1561
|
-
assert(
|
|
1562
|
-
divisionWorkOrderPipelineText.includes('"division"') && divisionWorkOrderPipelineText.includes('"$regex":"obsidian"') && !divisionWorkOrderPipelineText.includes('obsidian division'),
|
|
1563
|
-
'Expected Obsidian division wording to become a normalized division filter'
|
|
1564
|
-
);
|
|
1565
|
-
assert(
|
|
1566
|
-
!divisionWorkOrderPipelineText.includes('chemicals.shipped.quantity') && !divisionWorkOrderPipelineText.includes('$unwind'),
|
|
1567
|
-
'Expected plain work-order request not to use the shipped-chemical pipeline'
|
|
1568
|
-
);
|
|
1569
|
-
const openWorkOrderDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1570
|
-
'Show open work orders with planned or shipped chemicals',
|
|
1571
|
-
['work-order-dynamics'],
|
|
1572
|
-
collectionNames,
|
|
1573
|
-
'snf',
|
|
1574
|
-
{
|
|
1575
|
-
appDataRequestContract: {
|
|
1576
|
-
intent_type: 'data_summary',
|
|
1577
|
-
intent_id: 'snf_job_chemical_shipments',
|
|
1578
|
-
domain: 'snf_job_chemical_shipments',
|
|
1579
|
-
collection: 'work-order-dynamics',
|
|
1580
|
-
operation: 'aggregate',
|
|
1581
|
-
requested_breakdowns: [{ type: 'entity', requested_text: 'chemical', dimension: 'chemical' }],
|
|
1582
|
-
date_range: null,
|
|
1583
|
-
measures: ['qty_sent'],
|
|
1584
|
-
filters: [{ field: 'status', operator: 'contains', value: 'open' }],
|
|
1585
|
-
output_format: 'collapse_table',
|
|
1586
|
-
user_progress: [],
|
|
1587
|
-
ambiguities: [],
|
|
1588
|
-
confidence: 0.9,
|
|
1589
|
-
source: 'mini_model_classifier_test'
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
);
|
|
1593
|
-
const openWorkOrderPipelineText = JSON.stringify(openWorkOrderDirective?.payload?.pipeline || []);
|
|
1594
|
-
assert(openWorkOrderDirective?.payload?.collection === 'work-order-dynamics', 'Expected explicit open work-order chemical request to keep work-order-dynamics');
|
|
1595
|
-
assert(openWorkOrderDirective?.metadata?.customerText === '', 'Expected connector words like "with or" not to become customer text');
|
|
1596
|
-
assert(!openWorkOrderPipelineText.includes('"$regex":""'), 'Expected blank customer placeholders to be removed from configured pipelines');
|
|
1597
|
-
assert(openWorkOrderPipelineText.includes('$nin'), 'Expected open status to become an open-like not-completed status filter');
|
|
1598
|
-
assert(!openWorkOrderPipelineText.includes('"$in":["Completed","Reviewed","Closed"]'), 'Expected explicit open status to remove the recipe default closed-status match');
|
|
1599
|
-
const invoiceDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1600
|
-
'Devon Energy invoices this year',
|
|
1601
|
-
['invoices'],
|
|
1602
|
-
collectionNames,
|
|
1603
|
-
'snf'
|
|
1604
|
-
);
|
|
1605
|
-
assert(invoiceDirective?.payload?.collection === 'invoices', 'Expected invoice request to resolve to invoices');
|
|
1606
|
-
const chevronInvoiceChemicalDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1607
|
-
'How many chemicals did Chevron get billed for this year?',
|
|
1608
|
-
['invoices'],
|
|
1609
|
-
collectionNames,
|
|
1610
|
-
'snf'
|
|
1611
|
-
);
|
|
1612
|
-
const chevronInvoiceChemicalPipelineText = JSON.stringify(chevronInvoiceChemicalDirective?.payload?.pipeline || []);
|
|
1613
|
-
assert(
|
|
1614
|
-
String(chevronInvoiceChemicalDirective?.rawLine || '').includes('app-data-intent:snf_invoice_chemical_quantities'),
|
|
1615
|
-
'Expected billed-chemical wording to use the invoice chemical line-item workflow'
|
|
1616
|
-
);
|
|
1617
|
-
assert(chevronInvoiceChemicalPipelineText.includes('Chevron'), 'Expected customer entity Chevron to be applied to the invoice pipeline');
|
|
1618
|
-
assert(!chevronInvoiceChemicalPipelineText.includes('How many Chevron'), 'Expected customer extraction to remove question phrasing');
|
|
1619
|
-
const noisyDateFilterDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1620
|
-
'How many chemicals did Chevron get billed for this year?',
|
|
1621
|
-
['invoices'],
|
|
1622
|
-
collectionNames,
|
|
1623
|
-
'snf',
|
|
1624
|
-
{
|
|
1625
|
-
appDataRequestContract: {
|
|
1626
|
-
intent_type: 'data_summary',
|
|
1627
|
-
intent_id: 'snf_invoice_chemical_quantities',
|
|
1628
|
-
domain: 'snf_invoice_chemical_quantities',
|
|
1629
|
-
collection: 'invoices',
|
|
1630
|
-
operation: 'aggregate',
|
|
1631
|
-
requested_breakdowns: [],
|
|
1632
|
-
date_range: null,
|
|
1633
|
-
measures: ['quantity'],
|
|
1634
|
-
filters: [{ field: 'date_field', operator: 'contains', value: 'date_created' }],
|
|
1635
|
-
output_format: 'collapse_table',
|
|
1636
|
-
user_progress: [],
|
|
1637
|
-
ambiguities: [],
|
|
1638
|
-
confidence: 0.9,
|
|
1639
|
-
source: 'mini_model_classifier_test'
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
);
|
|
1643
|
-
assert(
|
|
1644
|
-
!JSON.stringify(noisyDateFilterDirective?.metadata?.requestContract?.filters || []).includes('date_field'),
|
|
1645
|
-
'Expected classifier date_field noise to be dropped from request filters'
|
|
1646
|
-
);
|
|
1647
|
-
const glutaraldehydeInvoiceDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1648
|
-
'Summarize May invoices for glutaraldehyde by customer',
|
|
1649
|
-
['invoices'],
|
|
1650
|
-
collectionNames,
|
|
1651
|
-
'snf',
|
|
1652
|
-
{
|
|
1653
|
-
appDataRequestContract: {
|
|
1654
|
-
intent_type: 'data_summary',
|
|
1655
|
-
intent_id: 'snf_invoice_customer_group_summary',
|
|
1656
|
-
domain: 'snf_invoice_customer_group_summary',
|
|
1657
|
-
collection: 'invoices',
|
|
1658
|
-
operation: 'aggregate',
|
|
1659
|
-
requested_breakdowns: [{ type: 'entity', requested_text: 'customer', dimension: 'customer' }],
|
|
1660
|
-
date_range: null,
|
|
1661
|
-
measures: ['quantity', 'invoice_total'],
|
|
1662
|
-
filters: [{ field: 'chemical', operator: 'contains', value: 'May glutaraldehyde' }],
|
|
1663
|
-
output_format: 'collapse_table',
|
|
1664
|
-
user_progress: [
|
|
1665
|
-
"I'll generate a summary of invoices for glutaraldehyde by customer for May 2023.",
|
|
1666
|
-
"I'll focus on invoice header totals and group the results by customer."
|
|
1667
|
-
],
|
|
1668
|
-
ambiguities: [],
|
|
1669
|
-
confidence: 0.9,
|
|
1670
|
-
source: 'mini_model_classifier_test'
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
);
|
|
1674
|
-
const glutaraldehydeInvoicePipelineText = JSON.stringify(glutaraldehydeInvoiceDirective?.payload?.pipeline || []);
|
|
1675
|
-
const glutaraldehydeInvoiceContractText = JSON.stringify(glutaraldehydeInvoiceDirective?.metadata?.requestContract || {});
|
|
1676
|
-
assert(
|
|
1677
|
-
glutaraldehydeInvoiceDirective?.metadata?.requestContract?.intent_id === 'snf_invoice_chemical_quantities',
|
|
1678
|
-
'Expected normalized request contract to report the selected invoice chemical workflow'
|
|
1679
|
-
);
|
|
1680
|
-
assert(glutaraldehydeInvoicePipelineText.includes('glutaraldehyde'), 'Expected chemical filter value to apply to invoice line items');
|
|
1681
|
-
assert(!glutaraldehydeInvoicePipelineText.includes('May glutaraldehyde'), 'Expected month text to be removed from chemical filter value');
|
|
1682
|
-
assert(glutaraldehydeInvoiceContractText.includes('"customer"'), 'Expected by-customer breakdown to remain in the contract');
|
|
1683
|
-
assert(!glutaraldehydeInvoiceContractText.includes('May 2023'), 'Expected wrong-year model progress to be rejected');
|
|
1684
|
-
assert(!glutaraldehydeInvoiceContractText.includes('header totals'), 'Expected header-total progress to be rejected for invoice chemical line items');
|
|
1685
|
-
const inventoryDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1686
|
-
'Break down glutaraldehyde inventory by location',
|
|
1687
|
-
['chemical-inventory-snapshots'],
|
|
1688
|
-
collectionNames,
|
|
1689
|
-
'snf'
|
|
1690
|
-
);
|
|
1691
|
-
const inventoryPipelineText = JSON.stringify(inventoryDirective?.payload?.pipeline || []);
|
|
1692
|
-
assert(inventoryDirective?.payload?.collection === 'chemical-inventory-snapshots', 'Expected bare chemical inventory request to use inventory snapshots');
|
|
1693
|
-
assert(inventoryPipelineText.includes('glutaraldehyde'), 'Expected bare chemical name to be preserved as an inventory chemical filter');
|
|
1694
|
-
assert(inventoryPipelineText.includes('location'), 'Expected inventory by-location breakdown to be applied');
|
|
1695
|
-
const plannedClassifierCleanupDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1696
|
-
'What chemicals were planned for Devon Energy jobs last month?',
|
|
1697
|
-
['jobs'],
|
|
1698
|
-
collectionNames,
|
|
1699
|
-
'snf',
|
|
1700
|
-
{
|
|
1701
|
-
appDataRequestContract: {
|
|
1702
|
-
intent_type: 'data_summary',
|
|
1703
|
-
intent_id: 'snf_planned_job_chemicals',
|
|
1704
|
-
domain: 'snf_planned_job_chemicals',
|
|
1705
|
-
collection: 'jobs',
|
|
1706
|
-
operation: 'aggregate',
|
|
1707
|
-
requested_breakdowns: [],
|
|
1708
|
-
date_range: null,
|
|
1709
|
-
measures: ['planned_quantity'],
|
|
1710
|
-
filters: [{ field: 'location', operator: 'contains', value: 'planned Devon Energy' }],
|
|
1711
|
-
output_format: 'collapse_table',
|
|
1712
|
-
user_progress: [],
|
|
1713
|
-
ambiguities: [],
|
|
1714
|
-
confidence: 0.7,
|
|
1715
|
-
source: 'mini_model_classifier_test'
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
);
|
|
1719
|
-
const plannedCleanupContractText = JSON.stringify(plannedClassifierCleanupDirective?.metadata?.requestContract || {});
|
|
1720
|
-
const plannedCleanupPipelineText = JSON.stringify(plannedClassifierCleanupDirective?.payload?.pipeline || []);
|
|
1721
|
-
assert(
|
|
1722
|
-
plannedCleanupContractText.includes('"field":"customer"'),
|
|
1723
|
-
`Expected planned job customer value to replace misclassified location filter: ${plannedCleanupContractText} ${plannedCleanupPipelineText}`
|
|
1724
|
-
);
|
|
1725
|
-
assert(!plannedCleanupContractText.includes('"field":"location"'), 'Expected stale misclassified location filter to be removed');
|
|
1726
|
-
assert(plannedCleanupPipelineText.includes('Devon.*Energy'), 'Expected planned job customer filter value to apply to the pipeline');
|
|
1727
|
-
assert(!plannedCleanupPipelineText.includes('planned Devon Energy'), 'Expected workflow word to be removed from planned customer filter value');
|
|
1728
|
-
const blendSummaryDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1729
|
-
'Give me a summary of all the chemical blends this month',
|
|
1730
|
-
['chemical-blends'],
|
|
1731
|
-
collectionNames,
|
|
1732
|
-
'snf'
|
|
1733
|
-
);
|
|
1734
|
-
const blendDateMatch = blendSummaryDirective?.payload?.pipeline?.[0]?.$match?.date;
|
|
1735
|
-
const now = new Date();
|
|
1736
|
-
const expectedThisMonthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1, 0, 0, 0, 0)).toISOString();
|
|
1737
|
-
const expectedThisMonthEnd = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 23, 59, 59, 999)).toISOString();
|
|
1738
|
-
assert(blendSummaryDirective?.payload?.collection === 'chemical-blends', 'Expected chemical blend summary request to resolve to chemical-blends');
|
|
1739
|
-
assert(blendDateMatch?.$gte instanceof Date, 'Expected configured date_start placeholder to remain a Date');
|
|
1740
|
-
assert(blendDateMatch?.$lte instanceof Date, 'Expected configured date_end placeholder to remain a Date');
|
|
1741
|
-
assert(blendDateMatch.$gte.toISOString() === expectedThisMonthStart, 'Expected this-month start date in configured pipeline');
|
|
1742
|
-
assert(blendDateMatch.$lte.toISOString() === expectedThisMonthEnd, 'Expected this-month end date in configured pipeline');
|
|
1743
|
-
const blendByChemicalDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1744
|
-
'Give me a summary of all the chemical blends this month by chemical',
|
|
1745
|
-
['chemical-blends'],
|
|
1746
|
-
collectionNames,
|
|
1747
|
-
'snf'
|
|
1748
|
-
);
|
|
1749
|
-
const blendByChemicalPipelineText = JSON.stringify(blendByChemicalDirective?.payload?.pipeline || []);
|
|
1750
|
-
assert(blendByChemicalDirective?.payload?.collection === 'chemical-blends', 'Expected by-chemical blend request to keep the chemical-blends collection');
|
|
1751
|
-
assert(blendByChemicalDirective?.metadata?.requestedDimension?.id === 'chemical', 'Expected by-chemical request to resolve the configured chemical dimension');
|
|
1752
|
-
assert(blendByChemicalPipelineText.includes('"chemical"'), 'Expected by-chemical request to group/project chemical');
|
|
1753
|
-
assert(!blendByChemicalPipelineText.includes('"status"'), 'Expected by-chemical request not to keep the default status grouping');
|
|
1754
|
-
const blendByMonthChemicalDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1755
|
-
'break down the chemical blends by month by chemical',
|
|
1756
|
-
['chemical-blends'],
|
|
1757
|
-
collectionNames,
|
|
1758
|
-
'snf'
|
|
1759
|
-
);
|
|
1760
|
-
const blendByMonthChemicalPipeline = blendByMonthChemicalDirective?.payload?.pipeline || [];
|
|
1761
|
-
const blendByMonthChemicalPipelineText = JSON.stringify(blendByMonthChemicalPipeline);
|
|
1762
|
-
const blendByMonthChemicalGroup = blendByMonthChemicalPipeline.find((stage: any) => !!stage?.$group)?.$group;
|
|
1763
|
-
assert(blendByMonthChemicalDirective?.payload?.collection === 'chemical-blends', 'Expected by-month by-chemical blend request to keep the chemical-blends collection');
|
|
1764
|
-
assert(blendByMonthChemicalGroup?._id?.month?.$dateToString?.format === '%Y-%m', 'Expected by-month by-chemical request to group by month');
|
|
1765
|
-
assert(blendByMonthChemicalGroup?._id?.month?.$dateToString?.date === '$date', 'Expected by-month grouping to use the configured date field');
|
|
1766
|
-
assert(blendByMonthChemicalGroup?._id?.chemical, 'Expected by-month by-chemical request to keep the chemical dimension in the group key');
|
|
1767
|
-
assert(blendByMonthChemicalPipelineText.includes('"month"'), 'Expected by-month by-chemical request to project month');
|
|
1768
|
-
assert(blendByMonthChemicalPipelineText.includes('"chemical"'), 'Expected by-month by-chemical request to project chemical');
|
|
1769
|
-
assert(!blendByMonthChemicalPipelineText.includes('"status"'), 'Expected by-month by-chemical request not to keep the default status grouping');
|
|
1770
|
-
assert(
|
|
1771
|
-
blendByMonthChemicalPipeline.find((stage: any) => !!stage?.$sort)?.$sort?.month === -1,
|
|
1772
|
-
'Expected by-month by-chemical request to sort newest month first'
|
|
1773
|
-
);
|
|
1774
|
-
assert(
|
|
1775
|
-
blendByMonthChemicalDirective?.metadata?.requestContract?.requested_breakdowns?.length === 2,
|
|
1776
|
-
'Expected by-month by-chemical request contract to preserve both requested breakdowns'
|
|
1777
|
-
);
|
|
1778
|
-
const classifierDrivenBlendDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1779
|
-
'chemical blends summary',
|
|
1780
|
-
['chemical-blends'],
|
|
1781
|
-
collectionNames,
|
|
1782
|
-
'snf',
|
|
1783
|
-
{
|
|
1784
|
-
appDataRequestContract: {
|
|
1785
|
-
intent_type: 'data_summary',
|
|
1786
|
-
intent_id: 'snf_chemical_blends_summary',
|
|
1787
|
-
domain: 'snf_chemical_blends_summary',
|
|
1788
|
-
collection: 'chemical-blends',
|
|
1789
|
-
operation: 'aggregate',
|
|
1790
|
-
requested_breakdowns: [
|
|
1791
|
-
{ type: 'time', requested_text: 'month', granularity: 'month', field_hint: 'date' },
|
|
1792
|
-
{ type: 'entity', requested_text: 'chemical', dimension: 'chemical', field_hints: ['chemical'] }
|
|
1793
|
-
],
|
|
1794
|
-
date_range: null,
|
|
1795
|
-
measures: ['quantity'],
|
|
1796
|
-
filters: [],
|
|
1797
|
-
output_format: 'collapse_table',
|
|
1798
|
-
ambiguities: [],
|
|
1799
|
-
confidence: 0.98,
|
|
1800
|
-
source: 'mini_model_classifier_test'
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
);
|
|
1804
|
-
const classifierDrivenPipeline = classifierDrivenBlendDirective?.payload?.pipeline || [];
|
|
1805
|
-
const classifierDrivenGroup = classifierDrivenPipeline.find((stage: any) => !!stage?.$group)?.$group;
|
|
1806
|
-
assert(classifierDrivenGroup?._id?.month?.$dateToString?.format === '%Y-%m', 'Expected classifier contract to drive month grouping');
|
|
1807
|
-
assert(classifierDrivenGroup?._id?.chemical, 'Expected classifier contract to drive chemical grouping');
|
|
1808
|
-
assert(
|
|
1809
|
-
classifierDrivenBlendDirective?.metadata?.requestContract?.source === 'mini_model_classifier_test',
|
|
1810
|
-
'Expected directive metadata to preserve classifier contract source'
|
|
1811
|
-
);
|
|
1812
|
-
assert(
|
|
1813
|
-
classifierDrivenBlendDirective?.metadata?.requestContract?.requested_breakdowns?.length === 2,
|
|
1814
|
-
'Expected classifier contract to preserve both requested breakdowns'
|
|
1815
|
-
);
|
|
1816
|
-
const classifierMessages = buildAssistantDataRequestClassifierMessagesForTesting({
|
|
1817
|
-
message: 'break down the chemical blends by month by chemical',
|
|
1818
|
-
appId: 'snf',
|
|
1819
|
-
collectionNames
|
|
1820
|
-
});
|
|
1821
|
-
const classifierPromptText = JSON.stringify(classifierMessages);
|
|
1822
|
-
assert(
|
|
1823
|
-
classifierPromptText.includes('by month by chemical') && classifierPromptText.includes('two requested_breakdowns'),
|
|
1824
|
-
'Expected classifier prompt to explicitly preserve multiple by clauses'
|
|
1825
|
-
);
|
|
1826
|
-
assert(
|
|
1827
|
-
classifierPromptText.includes('user_progress') && classifierPromptText.includes('first-person'),
|
|
1828
|
-
'Expected classifier prompt to request model-written user progress'
|
|
1829
|
-
);
|
|
1830
|
-
assert(
|
|
1831
|
-
classifierPromptText.includes('Do not restate the user command') && classifierPromptText.includes('I want'),
|
|
1832
|
-
'Expected classifier prompt to reject command-restatement progress'
|
|
1833
|
-
);
|
|
1834
|
-
const invoiceClassifierMessages = buildAssistantDataRequestClassifierMessagesForTesting({
|
|
1835
|
-
message: 'Group invoices this year by month by customer',
|
|
1836
|
-
appId: 'snf',
|
|
1837
|
-
collectionNames
|
|
1838
|
-
});
|
|
1839
|
-
assert(
|
|
1840
|
-
JSON.stringify(invoiceClassifierMessages).includes('snf_invoice_chemical_quantities'),
|
|
1841
|
-
'Expected classifier candidates to include unscored invoice app data intents'
|
|
1842
|
-
);
|
|
1843
|
-
const chemicalActivityDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1844
|
-
'summarize all the activity for 26% nacl brine this current year',
|
|
1845
|
-
['chemical-transactions'],
|
|
1846
|
-
collectionNames,
|
|
1847
|
-
'snf',
|
|
1848
|
-
{
|
|
1849
|
-
appDataRequestContract: {
|
|
1850
|
-
intent_type: 'data_summary',
|
|
1851
|
-
intent_id: 'snf_chemical_activity_summary',
|
|
1852
|
-
domain: 'snf_chemical_activity_summary',
|
|
1853
|
-
collection: 'chemical-transactions',
|
|
1854
|
-
operation: 'aggregate',
|
|
1855
|
-
requested_breakdowns: [],
|
|
1856
|
-
date_range: null,
|
|
1857
|
-
measures: ['quantity_diff'],
|
|
1858
|
-
filters: [{ field: 'chemical', operator: 'contains', value: '26% nacl brine' }],
|
|
1859
|
-
output_format: 'collapse_table',
|
|
1860
|
-
user_progress: [
|
|
1861
|
-
"I'm identifying this as chemical transaction activity for 26% NaCl brine.",
|
|
1862
|
-
"I'm applying the current-year date range and chemical filter before summarizing movement records."
|
|
1863
|
-
],
|
|
1864
|
-
ambiguities: [],
|
|
1865
|
-
confidence: 0.98,
|
|
1866
|
-
source: 'mini_model_classifier_test'
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
);
|
|
1870
|
-
const chemicalActivityPipelineText = JSON.stringify(chemicalActivityDirective?.payload?.pipeline || []);
|
|
1871
|
-
assert(chemicalActivityDirective?.payload?.collection === 'chemical-transactions', 'Expected chemical activity request to resolve to chemical-transactions');
|
|
1872
|
-
assert(chemicalActivityDirective?.payload?.permissionView === '/chemical/detail', 'Expected chemical activity request to use the chemical detail permission view');
|
|
1873
|
-
assert(chemicalActivityPipelineText.includes('quantity_diff'), 'Expected chemical activity request to include quantity_diff');
|
|
1874
|
-
assert(chemicalActivityPipelineText.includes('26%.*nacl.*brine'), 'Expected classifier chemical filter to apply to the activity pipeline');
|
|
1875
|
-
assert(
|
|
1876
|
-
chemicalActivityDirective?.metadata?.progress?.[0] === "I'm identifying this as chemical transaction activity for 26% NaCl brine.",
|
|
1877
|
-
'Expected classifier user_progress to drive visible directive progress'
|
|
1878
|
-
);
|
|
1879
|
-
assert(
|
|
1880
|
-
chemicalActivityDirective?.metadata?.requestContract?.output_format === 'collapse_table',
|
|
1881
|
-
'Expected chemical activity request to preserve collapse-table output format'
|
|
1882
|
-
);
|
|
1883
|
-
assert(
|
|
1884
|
-
chemicalActivityDirective?.metadata?.planValidation?.ok === true,
|
|
1885
|
-
'Expected app-data directive metadata to include successful query plan validation'
|
|
1886
|
-
);
|
|
1887
|
-
const leftoverFilterDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1888
|
-
'Show brine activity by month for this year',
|
|
1889
|
-
['chemical-transactions'],
|
|
1890
|
-
collectionNames,
|
|
1891
|
-
'snf',
|
|
1892
|
-
{
|
|
1893
|
-
appDataRequestContract: {
|
|
1894
|
-
intent_type: 'data_summary',
|
|
1895
|
-
intent_id: 'snf_chemical_activity_summary',
|
|
1896
|
-
domain: 'snf_chemical_activity_summary',
|
|
1897
|
-
collection: 'chemical-transactions',
|
|
1898
|
-
operation: 'aggregate',
|
|
1899
|
-
requested_breakdowns: [{ type: 'time', requested_text: 'month', dimension: 'month', granularity: 'month' }],
|
|
1900
|
-
date_range: null,
|
|
1901
|
-
measures: ['quantity_diff'],
|
|
1902
|
-
filters: [{ field: 'date', operator: 'contains', value: 'year-to-date' }],
|
|
1903
|
-
output_format: 'collapse_table',
|
|
1904
|
-
user_progress: [],
|
|
1905
|
-
ambiguities: [],
|
|
1906
|
-
confidence: 0.98,
|
|
1907
|
-
source: 'mini_model_classifier_test'
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
);
|
|
1911
|
-
assert(
|
|
1912
|
-
JSON.stringify(leftoverFilterDirective?.metadata?.requestContract?.filters || []).includes('brine'),
|
|
1913
|
-
'Expected likely leftover chemical text to be appended when classifier only emitted a date filter'
|
|
1914
|
-
);
|
|
1915
|
-
assert(
|
|
1916
|
-
JSON.stringify(leftoverFilterDirective?.payload?.pipeline || []).includes('brine'),
|
|
1917
|
-
'Expected appended leftover chemical filter to apply to the activity pipeline'
|
|
1918
|
-
);
|
|
1919
|
-
const positiveMovementDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1920
|
-
'Which chemical activity increased in last month?',
|
|
1921
|
-
['chemical-transactions'],
|
|
1922
|
-
collectionNames,
|
|
1923
|
-
'snf',
|
|
1924
|
-
{
|
|
1925
|
-
appDataRequestContract: {
|
|
1926
|
-
intent_type: 'data_summary',
|
|
1927
|
-
intent_id: 'snf_chemical_activity_summary',
|
|
1928
|
-
domain: 'snf_chemical_activity_summary',
|
|
1929
|
-
collection: 'chemical-transactions',
|
|
1930
|
-
operation: 'aggregate',
|
|
1931
|
-
requested_breakdowns: [],
|
|
1932
|
-
date_range: null,
|
|
1933
|
-
measures: ['quantity_diff'],
|
|
1934
|
-
filters: [{ field: 'quantity_diff', operator: 'contains', value: '>0' }],
|
|
1935
|
-
output_format: 'collapse_table',
|
|
1936
|
-
user_progress: [],
|
|
1937
|
-
ambiguities: [],
|
|
1938
|
-
confidence: 0.98,
|
|
1939
|
-
source: 'mini_model_classifier_test'
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
);
|
|
1943
|
-
const positiveMovementContractText = JSON.stringify(positiveMovementDirective?.metadata?.requestContract || {});
|
|
1944
|
-
const positiveMovementPipelineText = JSON.stringify(positiveMovementDirective?.payload?.pipeline || []);
|
|
1945
|
-
assert(!positiveMovementContractText.includes('increased in'), 'Expected movement wording not to become a chemical fallback filter');
|
|
1946
|
-
assert(positiveMovementPipelineText.includes('"$gt":0'), 'Expected numeric comparator text >0 to apply as a greater-than filter');
|
|
1947
|
-
assert(
|
|
1948
|
-
!(positiveMovementDirective?.metadata?.requestContract?.ambiguities || []).some((entry: string) => /quantity/i.test(entry) && /not applied/i.test(entry)),
|
|
1949
|
-
`Expected parsed numeric comparator filter not to be reported as unapplied: ${positiveMovementContractText} ${positiveMovementPipelineText}`
|
|
1950
|
-
);
|
|
1951
|
-
const badProgressDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
1952
|
-
'summarize all the activity for 26% nacl brine this current year',
|
|
1953
|
-
['chemical-transactions'],
|
|
1954
|
-
collectionNames,
|
|
1955
|
-
'snf',
|
|
1956
|
-
{
|
|
1957
|
-
appDataRequestContract: {
|
|
1958
|
-
intent_type: 'data_summary',
|
|
1959
|
-
intent_id: 'snf_chemical_activity_summary',
|
|
1960
|
-
domain: 'snf_chemical_activity_summary',
|
|
1961
|
-
collection: 'chemical-transactions',
|
|
1962
|
-
operation: 'aggregate',
|
|
1963
|
-
requested_breakdowns: [{ type: 'entity', requested_text: 'by activity type', dimension: 'activity_type' }],
|
|
1964
|
-
date_range: null,
|
|
1965
|
-
measures: ['quantity_diff'],
|
|
1966
|
-
filters: { chemical: '26% nacl brine' } as any,
|
|
1967
|
-
output_format: 'collapse_table',
|
|
1968
|
-
user_progress: [
|
|
1969
|
-
'i want to see the activity summary',
|
|
1970
|
-
'please include all relevant details for this year',
|
|
1971
|
-
'Reading your request',
|
|
1972
|
-
"I'll extract intent_id, date_range, and requested_breakdowns."
|
|
1973
|
-
],
|
|
1974
|
-
ambiguities: [],
|
|
1975
|
-
confidence: 0.98,
|
|
1976
|
-
source: 'mini_model_classifier_test'
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
);
|
|
1980
|
-
assert(
|
|
1981
|
-
!(badProgressDirective?.metadata?.requestContract?.user_progress || []).length,
|
|
1982
|
-
'Expected command-restatement and placeholder model progress to be rejected'
|
|
1983
|
-
);
|
|
1984
|
-
assert(
|
|
1985
|
-
JSON.stringify(badProgressDirective?.payload?.pipeline || []).includes('26%.*nacl.*brine'),
|
|
1986
|
-
'Expected object-form classifier filters to normalize and apply'
|
|
1987
|
-
);
|
|
1988
|
-
assert(
|
|
1989
|
-
!JSON.stringify(badProgressDirective?.metadata?.progress || []).includes('intent_id'),
|
|
1990
|
-
'Expected internal JSON field names to be removed from visible model progress'
|
|
1991
|
-
);
|
|
1992
|
-
const byByResponse = buildAssistantToolFallbackResponseForTesting({
|
|
1993
|
-
type: 'mongo_agg',
|
|
1994
|
-
input: { collection: 'chemical-transactions' },
|
|
1995
|
-
output: {
|
|
1996
|
-
collection: 'chemical-transactions',
|
|
1997
|
-
rowCount: 0,
|
|
1998
|
-
columns: ['Activity Type', 'Quantity Diff'],
|
|
1999
|
-
display: {
|
|
2000
|
-
columns: ['Activity Type', 'Quantity Diff'],
|
|
2001
|
-
rows: [],
|
|
2002
|
-
rowCount: 0,
|
|
2003
|
-
truncated: false,
|
|
2004
|
-
includeIds: false
|
|
2005
|
-
},
|
|
2006
|
-
debug: {
|
|
2007
|
-
requestContract: {
|
|
2008
|
-
requested_breakdowns: [{ type: 'entity', requested_text: 'by activity type', dimension: 'activity_type' }]
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
}
|
|
2012
|
-
});
|
|
2013
|
-
assert(
|
|
2014
|
-
byByResponse.includes('- Summary breakdown: by activity type') && !byByResponse.includes('by by activity type'),
|
|
2015
|
-
'Expected presentation formatting to avoid duplicated by/by wording'
|
|
2016
|
-
);
|
|
2017
|
-
const shortenedFilterDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2018
|
-
'summarize all the activity for 26% nacl brine this current year',
|
|
2019
|
-
['chemical-transactions'],
|
|
2020
|
-
collectionNames,
|
|
2021
|
-
'snf',
|
|
2022
|
-
{
|
|
2023
|
-
appDataRequestContract: {
|
|
2024
|
-
intent_type: 'data_summary',
|
|
2025
|
-
intent_id: 'snf_chemical_activity_summary',
|
|
2026
|
-
domain: 'snf_chemical_activity_summary',
|
|
2027
|
-
collection: 'chemical-transactions',
|
|
2028
|
-
operation: 'aggregate',
|
|
2029
|
-
requested_breakdowns: [{ type: 'entity', requested_text: 'activity_type', dimension: 'activity_type' }],
|
|
2030
|
-
date_range: { start: '2023-01-01', end: '2023-12-31', year: 2023 },
|
|
2031
|
-
measures: ['quantity_diff'],
|
|
2032
|
-
filters: [{ field: 'chemical', operator: 'contains', value: 'nacl brine' }],
|
|
2033
|
-
output_format: 'collapse_table',
|
|
2034
|
-
ambiguities: [],
|
|
2035
|
-
confidence: 0.98,
|
|
2036
|
-
source: 'mini_model_classifier_test'
|
|
2037
|
-
}
|
|
2038
|
-
}
|
|
2039
|
-
);
|
|
2040
|
-
assert(
|
|
2041
|
-
JSON.stringify(shortenedFilterDirective?.metadata?.requestContract?.filters || []).includes('26% nacl brine'),
|
|
2042
|
-
'Expected shortened classifier filter to preserve numeric/product qualifiers from the user request'
|
|
2043
|
-
);
|
|
2044
|
-
assert(
|
|
2045
|
-
shortenedFilterDirective?.metadata?.dateWindow?.year === new Date().getUTCFullYear(),
|
|
2046
|
-
'Expected deterministic relative-date parsing to override stale classifier date ranges'
|
|
2047
|
-
);
|
|
2048
|
-
const timeoutFallbackDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2049
|
-
'summarize all the activity for 26% nacl brine this current year',
|
|
2050
|
-
['chemical-transactions'],
|
|
2051
|
-
collectionNames,
|
|
2052
|
-
'snf',
|
|
2053
|
-
{
|
|
2054
|
-
appDataRequestClassifier: {
|
|
2055
|
-
attempted: true,
|
|
2056
|
-
succeeded: false,
|
|
2057
|
-
error: 'AI request timed out.'
|
|
2058
|
-
}
|
|
2059
|
-
}
|
|
2060
|
-
);
|
|
2061
|
-
assert(timeoutFallbackDirective, 'Expected classifier timeout to fall back to the app data intent instead of bypassing it');
|
|
2062
|
-
assert(
|
|
2063
|
-
JSON.stringify(timeoutFallbackDirective?.metadata?.requestContract?.filters || []).includes('26% nacl brine'),
|
|
2064
|
-
'Expected classifier timeout fallback to preserve likely filter text from the user request'
|
|
2065
|
-
);
|
|
2066
|
-
assert(
|
|
2067
|
-
JSON.stringify(timeoutFallbackDirective?.payload?.pipeline || []).includes('26%.*nacl.*brine'),
|
|
2068
|
-
'Expected classifier timeout fallback filter to apply to the activity pipeline'
|
|
2069
|
-
);
|
|
2070
|
-
const filterFieldBreakdownDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2071
|
-
'summarize all the activity for 26% nacl brine this current year',
|
|
2072
|
-
['chemical-transactions'],
|
|
2073
|
-
collectionNames,
|
|
2074
|
-
'snf',
|
|
2075
|
-
{
|
|
2076
|
-
appDataRequestContract: {
|
|
2077
|
-
intent_type: 'data_summary',
|
|
2078
|
-
intent_id: 'snf_chemical_activity_summary',
|
|
2079
|
-
domain: 'snf_chemical_activity_summary',
|
|
2080
|
-
collection: 'chemical-transactions',
|
|
2081
|
-
operation: 'aggregate',
|
|
2082
|
-
requested_breakdowns: [{ type: 'entity', requested_text: 'chemical', dimension: 'chemical' }],
|
|
2083
|
-
date_range: null,
|
|
2084
|
-
measures: ['quantity_diff'],
|
|
2085
|
-
filters: [{ field: 'chemical', operator: 'contains', value: '26% nacl brine' }],
|
|
2086
|
-
output_format: 'collapse_table',
|
|
2087
|
-
ambiguities: [],
|
|
2088
|
-
confidence: 0.98,
|
|
2089
|
-
source: 'mini_model_classifier_test'
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
);
|
|
2093
|
-
const filterFieldBreakdownGroup = filterFieldBreakdownDirective?.payload?.pipeline?.find((stage: any) => !!stage?.$group)?.$group;
|
|
2094
|
-
assert(
|
|
2095
|
-
!(filterFieldBreakdownDirective?.metadata?.requestContract?.requested_breakdowns || []).length,
|
|
2096
|
-
'Expected non-explicit classifier breakdowns that duplicate filter fields to be ignored'
|
|
2097
|
-
);
|
|
2098
|
-
assert(
|
|
2099
|
-
filterFieldBreakdownGroup?._id?.activity_type && filterFieldBreakdownGroup?._id?.type,
|
|
2100
|
-
'Expected ignored filter-field breakdowns to preserve the app recipe default activity grouping'
|
|
2101
|
-
);
|
|
2102
|
-
const plannedDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2103
|
-
'planned chemicals for Devon jobs',
|
|
2104
|
-
['jobs'],
|
|
2105
|
-
collectionNames,
|
|
2106
|
-
'snf'
|
|
2107
|
-
);
|
|
2108
|
-
assert(plannedDirective?.payload?.collection === 'jobs', 'Expected planned chemicals request to resolve to jobs');
|
|
2109
|
-
const plannedOpenDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2110
|
-
'Show open jobs with planned chemicals by customer',
|
|
2111
|
-
['jobs'],
|
|
2112
|
-
collectionNames,
|
|
2113
|
-
'snf',
|
|
2114
|
-
{
|
|
2115
|
-
appDataRequestClassifier: {
|
|
2116
|
-
attempted: true,
|
|
2117
|
-
succeeded: false,
|
|
2118
|
-
error: 'AI request timed out.'
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
);
|
|
2122
|
-
const plannedOpenPipelineText = JSON.stringify(plannedOpenDirective?.payload?.pipeline || []);
|
|
2123
|
-
assert(plannedOpenDirective?.payload?.collection === 'jobs', 'Expected open planned jobs request to resolve to jobs');
|
|
2124
|
-
assert(plannedOpenPipelineText.includes('"status"') && plannedOpenPipelineText.includes('open'), 'Expected open planned jobs fallback to preserve status filter evidence');
|
|
2125
|
-
}
|
|
2126
|
-
finally {
|
|
2127
|
-
if (priorInline === undefined) {
|
|
2128
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
2129
|
-
}
|
|
2130
|
-
else {
|
|
2131
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = priorInline;
|
|
2132
|
-
}
|
|
2133
|
-
if (priorFile === undefined) {
|
|
2134
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
2135
|
-
}
|
|
2136
|
-
else {
|
|
2137
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = priorFile;
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
}
|
|
2141
|
-
|
|
2142
|
-
function testAssistantAppDataIntentContractsPreserveGenericBreakdownsAndDateRanges() {
|
|
2143
|
-
const priorInline = process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
2144
|
-
const priorFile = process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
2145
|
-
try {
|
|
2146
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = '';
|
|
2147
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = JSON.stringify({
|
|
2148
|
-
apps: {
|
|
2149
|
-
demo: {
|
|
2150
|
-
enabled_deterministic_heuristics: ['app_data_intent'],
|
|
2151
|
-
data_intents: [
|
|
2152
|
-
{
|
|
2153
|
-
id: 'demo_order_summary',
|
|
2154
|
-
terms: ['orders', 'order summary'],
|
|
2155
|
-
collection: 'orders',
|
|
2156
|
-
permission_view: '/orders',
|
|
2157
|
-
date_field: 'date',
|
|
2158
|
-
quantity_field: 'total',
|
|
2159
|
-
dimensions: {
|
|
2160
|
-
customer_group: {
|
|
2161
|
-
terms: ['customer group', 'customer groups'],
|
|
2162
|
-
group_id: {
|
|
2163
|
-
customer_group: {
|
|
2164
|
-
$ifNull: ['$customer_group', 'Unknown']
|
|
2165
|
-
}
|
|
2166
|
-
},
|
|
2167
|
-
project: {
|
|
2168
|
-
customer_group: '$_id.customer_group'
|
|
2169
|
-
},
|
|
2170
|
-
sort: {
|
|
2171
|
-
customer_group: 1
|
|
2172
|
-
}
|
|
2173
|
-
},
|
|
2174
|
-
status: {
|
|
2175
|
-
terms: ['status', 'state'],
|
|
2176
|
-
group_id: {
|
|
2177
|
-
status: {
|
|
2178
|
-
$ifNull: ['$status', 'Unknown']
|
|
2179
|
-
}
|
|
2180
|
-
},
|
|
2181
|
-
project: {
|
|
2182
|
-
status: '$_id.status'
|
|
2183
|
-
},
|
|
2184
|
-
sort: {
|
|
2185
|
-
status: 1
|
|
2186
|
-
}
|
|
2187
|
-
}
|
|
2188
|
-
},
|
|
2189
|
-
filter_fields: {
|
|
2190
|
-
priority: {
|
|
2191
|
-
terms: ['priority'],
|
|
2192
|
-
field: 'priority'
|
|
2193
|
-
}
|
|
2194
|
-
},
|
|
2195
|
-
pipeline: [
|
|
2196
|
-
{
|
|
2197
|
-
$match: {
|
|
2198
|
-
date: {
|
|
2199
|
-
$gte: '{{date_start}}',
|
|
2200
|
-
$lte: '{{date_end}}'
|
|
2201
|
-
}
|
|
2202
|
-
}
|
|
2203
|
-
},
|
|
2204
|
-
{
|
|
2205
|
-
$group: {
|
|
2206
|
-
_id: null,
|
|
2207
|
-
order_count: { $sum: 1 },
|
|
2208
|
-
total: { $sum: '$total' }
|
|
2209
|
-
}
|
|
2210
|
-
},
|
|
2211
|
-
{
|
|
2212
|
-
$project: {
|
|
2213
|
-
_id: 0,
|
|
2214
|
-
order_count: 1,
|
|
2215
|
-
total: 1
|
|
2216
|
-
}
|
|
2217
|
-
},
|
|
2218
|
-
{
|
|
2219
|
-
$sort: {
|
|
2220
|
-
total: -1
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
]
|
|
2224
|
-
},
|
|
2225
|
-
{
|
|
2226
|
-
id: 'demo_order_list',
|
|
2227
|
-
terms: ['order list', 'orders list'],
|
|
2228
|
-
collection: 'orders',
|
|
2229
|
-
permission_view: '/orders',
|
|
2230
|
-
date_field: 'date',
|
|
2231
|
-
dimensions: {
|
|
2232
|
-
status: {
|
|
2233
|
-
terms: ['status', 'state'],
|
|
2234
|
-
group_id: {
|
|
2235
|
-
status: {
|
|
2236
|
-
$ifNull: ['$status', 'Unknown']
|
|
2237
|
-
}
|
|
2238
|
-
},
|
|
2239
|
-
project: {
|
|
2240
|
-
status: '$_id.status'
|
|
2241
|
-
},
|
|
2242
|
-
sort: {
|
|
2243
|
-
status: 1
|
|
2244
|
-
}
|
|
2245
|
-
}
|
|
2246
|
-
}
|
|
2247
|
-
}
|
|
2248
|
-
]
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
});
|
|
2252
|
-
const collectionNames = ['orders'];
|
|
2253
|
-
const byCustomerMonth = resolveAssistantHeuristicDirectiveForTesting(
|
|
2254
|
-
'Summarize orders by customer group by month',
|
|
2255
|
-
['orders'],
|
|
2256
|
-
collectionNames,
|
|
2257
|
-
'demo'
|
|
2258
|
-
);
|
|
2259
|
-
const byCustomerMonthPipeline = byCustomerMonth?.payload?.pipeline || [];
|
|
2260
|
-
const byCustomerMonthGroup = byCustomerMonthPipeline.find((stage: any) => !!stage?.$group)?.$group;
|
|
2261
|
-
assert(byCustomerMonth?.payload?.collection === 'orders', 'Expected generic app data request to keep the app-owned collection');
|
|
2262
|
-
assert(byCustomerMonth?.metadata?.customerText === '', 'Expected customer group breakdown text not to become a customer filter');
|
|
2263
|
-
assert(byCustomerMonthGroup?._id?.customer_group, 'Expected generic customer group breakdown to be included');
|
|
2264
|
-
assert(byCustomerMonthGroup?._id?.month?.$dateToString?.format === '%Y-%m', 'Expected generic month breakdown to be included');
|
|
2265
|
-
assert(
|
|
2266
|
-
byCustomerMonth?.metadata?.requestContract?.requested_breakdowns?.length === 2,
|
|
2267
|
-
'Expected generic contract to preserve customer group and month breakdowns'
|
|
2268
|
-
);
|
|
2269
|
-
assert(
|
|
2270
|
-
!(byCustomerMonth?.metadata?.requestContract?.ambiguities || []).length,
|
|
2271
|
-
'Expected applied customer group and month breakdowns not to record ambiguities'
|
|
2272
|
-
);
|
|
2273
|
-
|
|
2274
|
-
const classifierAndTextDuplicateDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2275
|
-
'Summarize orders by customer group by month',
|
|
2276
|
-
['orders'],
|
|
2277
|
-
collectionNames,
|
|
2278
|
-
'demo',
|
|
2279
|
-
{
|
|
2280
|
-
appDataRequestContract: {
|
|
2281
|
-
intent_type: 'data_summary',
|
|
2282
|
-
intent_id: 'demo_order_summary',
|
|
2283
|
-
domain: 'demo_order_summary',
|
|
2284
|
-
collection: 'orders',
|
|
2285
|
-
operation: 'aggregate',
|
|
2286
|
-
requested_breakdowns: [
|
|
2287
|
-
{ type: 'entity', requested_text: 'customer group', dimension: 'customer_group', field_hints: ['customer_group'] },
|
|
2288
|
-
{ type: 'time', requested_text: 'month', granularity: 'month', field_hint: 'date' }
|
|
2289
|
-
],
|
|
2290
|
-
date_range: null,
|
|
2291
|
-
measures: ['total'],
|
|
2292
|
-
filters: [],
|
|
2293
|
-
output_format: 'collapse_table',
|
|
2294
|
-
ambiguities: [],
|
|
2295
|
-
confidence: 0.98,
|
|
2296
|
-
source: 'mini_model_classifier_test'
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
);
|
|
2300
|
-
assert(
|
|
2301
|
-
classifierAndTextDuplicateDirective?.metadata?.requestContract?.requested_breakdowns?.length === 2,
|
|
2302
|
-
'Expected classifier and text-extracted breakdowns to dedupe to the two unique requested dimensions'
|
|
2303
|
-
);
|
|
2304
|
-
|
|
2305
|
-
const byStatusDay = resolveAssistantHeuristicDirectiveForTesting(
|
|
2306
|
-
'Show orders by status by day for past 30 days',
|
|
2307
|
-
['orders'],
|
|
2308
|
-
collectionNames,
|
|
2309
|
-
'demo'
|
|
2310
|
-
);
|
|
2311
|
-
const byStatusDayPipeline = byStatusDay?.payload?.pipeline || [];
|
|
2312
|
-
const byStatusDayGroup = byStatusDayPipeline.find((stage: any) => !!stage?.$group)?.$group;
|
|
2313
|
-
const byStatusDayMatch = byStatusDayPipeline.find((stage: any) => !!stage?.$match)?.$match?.date;
|
|
2314
|
-
assert(byStatusDayGroup?._id?.status, 'Expected generic status breakdown to be included');
|
|
2315
|
-
assert(byStatusDayGroup?._id?.day?.$dateToString?.format === '%Y-%m-%d', 'Expected generic day breakdown to be included');
|
|
2316
|
-
assert(byStatusDayMatch?.$gte instanceof Date, 'Expected past-30-days date start placeholder to become a Date');
|
|
2317
|
-
assert(byStatusDayMatch?.$lte instanceof Date, 'Expected past-30-days date end placeholder to become a Date');
|
|
2318
|
-
assert(
|
|
2319
|
-
byStatusDay?.metadata?.requestContract?.requested_breakdowns?.length === 2,
|
|
2320
|
-
'Expected generic contract to preserve status and day breakdowns'
|
|
2321
|
-
);
|
|
2322
|
-
|
|
2323
|
-
const unappliedBreakdownDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2324
|
-
'Show order list by status',
|
|
2325
|
-
['orders'],
|
|
2326
|
-
collectionNames,
|
|
2327
|
-
'demo'
|
|
2328
|
-
);
|
|
2329
|
-
const unappliedAmbiguities = unappliedBreakdownDirective?.metadata?.requestContract?.ambiguities || [];
|
|
2330
|
-
assert(unappliedBreakdownDirective?.payload?.collection === 'orders', 'Expected order list request to keep the app-owned collection');
|
|
2331
|
-
assert(unappliedBreakdownDirective?.metadata?.requestContract?.requested_breakdowns?.length === 1, 'Expected order list request to preserve the explicit status breakdown');
|
|
2332
|
-
assert(
|
|
2333
|
-
unappliedAmbiguities.some((entry: string) => /status/i.test(entry) && /not applied/i.test(entry)),
|
|
2334
|
-
'Expected an explicit ambiguity when a requested breakdown is not applied to the final group'
|
|
2335
|
-
);
|
|
2336
|
-
|
|
2337
|
-
const throughMay = resolveAssistantHeuristicDirectiveForTesting(
|
|
2338
|
-
'Summarize orders through May',
|
|
2339
|
-
['orders'],
|
|
2340
|
-
collectionNames,
|
|
2341
|
-
'demo'
|
|
2342
|
-
);
|
|
2343
|
-
const throughMayMatch = throughMay?.payload?.pipeline?.find((stage: any) => !!stage?.$match)?.$match?.date;
|
|
2344
|
-
const now = new Date();
|
|
2345
|
-
const expectedMayEnd = new Date(Date.UTC(now.getUTCFullYear(), 5, 0, 23, 59, 59, 999)).toISOString();
|
|
2346
|
-
const expectedCurrentMonthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1, 0, 0, 0, 0)).toISOString();
|
|
2347
|
-
const expectedCurrentDayEnd = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), 23, 59, 59, 999)).toISOString();
|
|
2348
|
-
assert(throughMayMatch?.$lte instanceof Date, 'Expected through-May date end placeholder to become a Date');
|
|
2349
|
-
assert(throughMayMatch.$lte.toISOString() === expectedMayEnd, 'Expected through-May date range to end on the last day of May');
|
|
2350
|
-
assert(!Object.prototype.hasOwnProperty.call(throughMayMatch, '$gte'), 'Expected through-May date range not to invent a start date');
|
|
2351
|
-
|
|
2352
|
-
const untilJune15 = resolveAssistantHeuristicDirectiveForTesting(
|
|
2353
|
-
'Summarize orders until June 15',
|
|
2354
|
-
['orders'],
|
|
2355
|
-
collectionNames,
|
|
2356
|
-
'demo'
|
|
2357
|
-
);
|
|
2358
|
-
const untilJune15Match = untilJune15?.payload?.pipeline?.find((stage: any) => !!stage?.$match)?.$match?.date;
|
|
2359
|
-
const expectedJune15End = new Date(Date.UTC(now.getUTCFullYear(), 5, 15, 23, 59, 59, 999)).toISOString();
|
|
2360
|
-
assert(untilJune15Match?.$lte instanceof Date, 'Expected until-June-15 date end placeholder to become a Date');
|
|
2361
|
-
assert(untilJune15Match.$lte.toISOString() === expectedJune15End, 'Expected until-June-15 date range to end on June 15');
|
|
2362
|
-
assert(!Object.prototype.hasOwnProperty.call(untilJune15Match, '$gte'), 'Expected until-June-15 date range not to invent a start date');
|
|
2363
|
-
|
|
2364
|
-
const thisMonth = resolveAssistantHeuristicDirectiveForTesting(
|
|
2365
|
-
'Summarize orders this month',
|
|
2366
|
-
['orders'],
|
|
2367
|
-
collectionNames,
|
|
2368
|
-
'demo'
|
|
2369
|
-
);
|
|
2370
|
-
const thisMonthMatch = thisMonth?.payload?.pipeline?.find((stage: any) => !!stage?.$match)?.$match?.date;
|
|
2371
|
-
assert(thisMonthMatch?.$gte instanceof Date, 'Expected this-month date start placeholder to become a Date');
|
|
2372
|
-
assert(thisMonthMatch?.$lte instanceof Date, 'Expected this-month date end placeholder to become a Date');
|
|
2373
|
-
assert(thisMonthMatch.$gte.toISOString() === expectedCurrentMonthStart, 'Expected this-month range to start on the first day of the current month');
|
|
2374
|
-
assert(thisMonthMatch.$lte.toISOString() === expectedCurrentDayEnd, 'Expected this-month range to end today');
|
|
2375
|
-
|
|
2376
|
-
const lastMonth = resolveAssistantHeuristicDirectiveForTesting(
|
|
2377
|
-
'Summarize orders last month',
|
|
2378
|
-
['orders'],
|
|
2379
|
-
collectionNames,
|
|
2380
|
-
'demo'
|
|
2381
|
-
);
|
|
2382
|
-
const lastMonthMatch = lastMonth?.payload?.pipeline?.find((stage: any) => !!stage?.$match)?.$match?.date;
|
|
2383
|
-
const expectedLastMonthStartDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 1, 0, 0, 0, 0));
|
|
2384
|
-
const expectedLastMonthEndDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 0, 23, 59, 59, 999));
|
|
2385
|
-
assert(lastMonthMatch?.$gte instanceof Date, 'Expected last-month date start placeholder to become a Date');
|
|
2386
|
-
assert(lastMonthMatch?.$lte instanceof Date, 'Expected last-month date end placeholder to become a Date');
|
|
2387
|
-
assert(lastMonthMatch.$gte.toISOString() === expectedLastMonthStartDate.toISOString(), 'Expected last-month range to start on the first day of last month');
|
|
2388
|
-
assert(lastMonthMatch.$lte.toISOString() === expectedLastMonthEndDate.toISOString(), 'Expected last-month range to end on the last day of last month');
|
|
2389
|
-
|
|
2390
|
-
const thisYearThroughMay = resolveAssistantHeuristicDirectiveForTesting(
|
|
2391
|
-
'Summarize orders this year through May',
|
|
2392
|
-
['orders'],
|
|
2393
|
-
collectionNames,
|
|
2394
|
-
'demo'
|
|
2395
|
-
);
|
|
2396
|
-
const thisYearThroughMayMatch = thisYearThroughMay?.payload?.pipeline?.find((stage: any) => !!stage?.$match)?.$match?.date;
|
|
2397
|
-
const expectedYearStart = new Date(Date.UTC(now.getUTCFullYear(), 0, 1, 0, 0, 0, 0)).toISOString();
|
|
2398
|
-
assert(thisYearThroughMayMatch?.$gte instanceof Date, 'Expected this-year-through-May date start placeholder to become a Date');
|
|
2399
|
-
assert(thisYearThroughMayMatch?.$lte instanceof Date, 'Expected this-year-through-May date end placeholder to become a Date');
|
|
2400
|
-
assert(thisYearThroughMayMatch.$gte.toISOString() === expectedYearStart, 'Expected this-year-through-May range to start at year start');
|
|
2401
|
-
assert(thisYearThroughMayMatch.$lte.toISOString() === expectedMayEnd, 'Expected this-year-through-May range to end on the last day of May');
|
|
2402
|
-
|
|
2403
|
-
const classifierDateRangeDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2404
|
-
'Summarize orders',
|
|
2405
|
-
['orders'],
|
|
2406
|
-
collectionNames,
|
|
2407
|
-
'demo',
|
|
2408
|
-
{
|
|
2409
|
-
appDataRequestContract: {
|
|
2410
|
-
intent_type: 'data_summary',
|
|
2411
|
-
intent_id: 'demo_order_summary',
|
|
2412
|
-
domain: 'demo_order_summary',
|
|
2413
|
-
collection: 'orders',
|
|
2414
|
-
operation: 'aggregate',
|
|
2415
|
-
requested_breakdowns: [
|
|
2416
|
-
{ type: 'entity', requested_text: 'customer group', dimension: 'customer_group', field_hints: ['customer_group'] },
|
|
2417
|
-
{ type: 'time', requested_text: 'month', granularity: 'month', field_hint: 'date' }
|
|
2418
|
-
],
|
|
2419
|
-
date_range: {
|
|
2420
|
-
start: '2026-04-01T00:00:00.000Z',
|
|
2421
|
-
end: '2026-04-30T23:59:59.999Z'
|
|
2422
|
-
},
|
|
2423
|
-
measures: ['total'],
|
|
2424
|
-
filters: [],
|
|
2425
|
-
output_format: 'collapse_table',
|
|
2426
|
-
ambiguities: [],
|
|
2427
|
-
confidence: 0.98,
|
|
2428
|
-
source: 'mini_model_classifier_test'
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
);
|
|
2432
|
-
const classifierDateRangePipeline = classifierDateRangeDirective?.payload?.pipeline || [];
|
|
2433
|
-
const classifierDateRangeMatch = classifierDateRangePipeline.find((stage: any) => !!stage?.$match)?.$match?.date;
|
|
2434
|
-
const classifierDateRangeGroup = classifierDateRangePipeline.find((stage: any) => !!stage?.$group)?.$group;
|
|
2435
|
-
assert(classifierDateRangeMatch?.$gte instanceof Date, 'Expected classifier date_range start to replace date_start placeholder as a Date');
|
|
2436
|
-
assert(classifierDateRangeMatch?.$lte instanceof Date, 'Expected classifier date_range end to replace date_end placeholder as a Date');
|
|
2437
|
-
assert(classifierDateRangeMatch.$gte.toISOString() === '2026-04-01T00:00:00.000Z', 'Expected classifier date_range start to drive the pipeline');
|
|
2438
|
-
assert(classifierDateRangeMatch.$lte.toISOString() === '2026-04-30T23:59:59.999Z', 'Expected classifier date_range end to drive the pipeline');
|
|
2439
|
-
assert(classifierDateRangeGroup?._id?.customer_group, 'Expected classifier customer-group breakdown to drive grouping');
|
|
2440
|
-
assert(classifierDateRangeGroup?._id?.month?.$dateToString?.format === '%Y-%m', 'Expected classifier month breakdown to drive grouping');
|
|
2441
|
-
assert(
|
|
2442
|
-
classifierDateRangeDirective?.metadata?.dateWindow?.year === 2026,
|
|
2443
|
-
'Expected classifier date_range to populate effective dateWindow year'
|
|
2444
|
-
);
|
|
2445
|
-
|
|
2446
|
-
const appliedDimensionFilterDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2447
|
-
'Summarize orders',
|
|
2448
|
-
['orders'],
|
|
2449
|
-
collectionNames,
|
|
2450
|
-
'demo',
|
|
2451
|
-
{
|
|
2452
|
-
appDataRequestContract: {
|
|
2453
|
-
intent_type: 'data_summary',
|
|
2454
|
-
intent_id: 'demo_order_summary',
|
|
2455
|
-
domain: 'demo_order_summary',
|
|
2456
|
-
collection: 'orders',
|
|
2457
|
-
operation: 'aggregate',
|
|
2458
|
-
requested_breakdowns: [],
|
|
2459
|
-
date_range: null,
|
|
2460
|
-
measures: ['total'],
|
|
2461
|
-
filters: [{ status: 'Open' }],
|
|
2462
|
-
output_format: 'collapse_table',
|
|
2463
|
-
ambiguities: [],
|
|
2464
|
-
confidence: 0.98,
|
|
2465
|
-
source: 'mini_model_classifier_test'
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
);
|
|
2469
|
-
const appliedDimensionFilterPipelineText = JSON.stringify(appliedDimensionFilterDirective?.payload?.pipeline || []);
|
|
2470
|
-
assert(appliedDimensionFilterPipelineText.includes('"status"'), 'Expected dimension-backed classifier filter to apply status to the pipeline');
|
|
2471
|
-
assert(appliedDimensionFilterPipelineText.includes('Open'), 'Expected dimension-backed classifier filter value to apply to the pipeline');
|
|
2472
|
-
assert(
|
|
2473
|
-
!(appliedDimensionFilterDirective?.metadata?.requestContract?.ambiguities || []).some((entry: string) => /status/i.test(entry) && /not applied/i.test(entry)),
|
|
2474
|
-
'Expected applied dimension-backed classifier filter not to record a not-applied ambiguity'
|
|
2475
|
-
);
|
|
2476
|
-
|
|
2477
|
-
const appliedExplicitFilterDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2478
|
-
'Summarize orders',
|
|
2479
|
-
['orders'],
|
|
2480
|
-
collectionNames,
|
|
2481
|
-
'demo',
|
|
2482
|
-
{
|
|
2483
|
-
appDataRequestContract: {
|
|
2484
|
-
intent_type: 'data_summary',
|
|
2485
|
-
intent_id: 'demo_order_summary',
|
|
2486
|
-
domain: 'demo_order_summary',
|
|
2487
|
-
collection: 'orders',
|
|
2488
|
-
operation: 'aggregate',
|
|
2489
|
-
requested_breakdowns: [],
|
|
2490
|
-
date_range: null,
|
|
2491
|
-
measures: ['total'],
|
|
2492
|
-
filters: [{ field: 'priority', operator: 'eq', value: 'High' }],
|
|
2493
|
-
output_format: 'collapse_table',
|
|
2494
|
-
ambiguities: [],
|
|
2495
|
-
confidence: 0.98,
|
|
2496
|
-
source: 'mini_model_classifier_test'
|
|
2497
|
-
}
|
|
2498
|
-
}
|
|
2499
|
-
);
|
|
2500
|
-
const appliedExplicitFilterPipelineText = JSON.stringify(appliedExplicitFilterDirective?.payload?.pipeline || []);
|
|
2501
|
-
assert(appliedExplicitFilterPipelineText.includes('"priority"'), 'Expected explicit app filter field to apply priority to the pipeline');
|
|
2502
|
-
assert(appliedExplicitFilterPipelineText.includes('High'), 'Expected explicit app filter value to apply to the pipeline');
|
|
2503
|
-
|
|
2504
|
-
const unappliedFilterDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2505
|
-
'Summarize orders',
|
|
2506
|
-
['orders'],
|
|
2507
|
-
collectionNames,
|
|
2508
|
-
'demo',
|
|
2509
|
-
{
|
|
2510
|
-
appDataRequestContract: {
|
|
2511
|
-
intent_type: 'data_summary',
|
|
2512
|
-
intent_id: 'demo_order_summary',
|
|
2513
|
-
domain: 'demo_order_summary',
|
|
2514
|
-
collection: 'orders',
|
|
2515
|
-
operation: 'aggregate',
|
|
2516
|
-
requested_breakdowns: [],
|
|
2517
|
-
date_range: null,
|
|
2518
|
-
measures: ['total'],
|
|
2519
|
-
filters: [{ region: 'North' }],
|
|
2520
|
-
output_format: 'collapse_table',
|
|
2521
|
-
ambiguities: [],
|
|
2522
|
-
confidence: 0.98,
|
|
2523
|
-
source: 'mini_model_classifier_test'
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
);
|
|
2527
|
-
const unappliedFilterAmbiguities = unappliedFilterDirective?.metadata?.requestContract?.ambiguities || [];
|
|
2528
|
-
assert(
|
|
2529
|
-
unappliedFilterDirective?.metadata?.requestContract?.filters?.[0]?.region === 'North',
|
|
2530
|
-
'Expected unmatched classifier filters to be preserved in the request contract'
|
|
2531
|
-
);
|
|
2532
|
-
assert(
|
|
2533
|
-
unappliedFilterAmbiguities.some((entry: string) => /filter/i.test(entry) && /region/i.test(entry) && /not applied/i.test(entry)),
|
|
2534
|
-
'Expected an explicit ambiguity when a classifier filter is not applied to the final pipeline'
|
|
2535
|
-
);
|
|
2536
|
-
assert(
|
|
2537
|
-
unappliedFilterDirective?.metadata?.requestContract?.confidence <= 0.65,
|
|
2538
|
-
'Expected unapplied classifier filters to lower request contract confidence'
|
|
2539
|
-
);
|
|
2540
|
-
}
|
|
2541
|
-
finally {
|
|
2542
|
-
if (priorInline === undefined) {
|
|
2543
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
2544
|
-
}
|
|
2545
|
-
else {
|
|
2546
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = priorInline;
|
|
2547
|
-
}
|
|
2548
|
-
if (priorFile === undefined) {
|
|
2549
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
2550
|
-
}
|
|
2551
|
-
else {
|
|
2552
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = priorFile;
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
}
|
|
2556
|
-
|
|
2557
|
-
function testAssistantRemovedDomainHeuristicIdsRemainAppSpecific() {
|
|
2558
|
-
const priorInline = process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
2559
|
-
const priorFile = process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
2560
|
-
try {
|
|
2561
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = '';
|
|
2562
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = JSON.stringify({
|
|
2563
|
-
apps: {
|
|
2564
|
-
snf: {
|
|
2565
|
-
enabled_deterministic_heuristics: [
|
|
2566
|
-
'active_clients_count',
|
|
2567
|
-
'current_bookings_count',
|
|
2568
|
-
'active_holds_count',
|
|
2569
|
-
'inventory_chemical_quantity',
|
|
2570
|
-
'work_order_week_customer_breakdown',
|
|
2571
|
-
'work_order_created_week_status',
|
|
2572
|
-
'work_order_completed_per_day',
|
|
2573
|
-
'work_order_completed_delivery_breakdown',
|
|
2574
|
-
'work_order_top_customers_window',
|
|
2575
|
-
'deliveries_per_driver_last_month',
|
|
2576
|
-
'active_jobs_count',
|
|
2577
|
-
'revenue_division_top_customers_window'
|
|
2578
|
-
]
|
|
2579
|
-
}
|
|
2580
|
-
}
|
|
2581
|
-
});
|
|
2582
|
-
const cases = [
|
|
2583
|
-
{
|
|
2584
|
-
message: 'How many active clients are there?',
|
|
2585
|
-
collectionHints: ['clients', 'customers'],
|
|
2586
|
-
collectionNames: ['clients', 'customers'],
|
|
2587
|
-
reason: 'Expected shared library to avoid emitting active client count directives'
|
|
2588
|
-
},
|
|
2589
|
-
{
|
|
2590
|
-
message: 'How many current bookings does Acme have?',
|
|
2591
|
-
collectionHints: ['bookings'],
|
|
2592
|
-
collectionNames: ['bookings'],
|
|
2593
|
-
reason: 'Expected shared library to avoid emitting booking count directives'
|
|
2594
|
-
},
|
|
2595
|
-
{
|
|
2596
|
-
message: 'How many active holds are there?',
|
|
2597
|
-
collectionHints: ['chemical-holds', 'holds'],
|
|
2598
|
-
collectionNames: ['chemical-holds', 'holds'],
|
|
2599
|
-
reason: 'Expected shared library to avoid emitting active hold directives'
|
|
2600
|
-
},
|
|
2601
|
-
{
|
|
2602
|
-
message: 'How many Sack -1760 of FLOJET DRMAX P 9115 does midland plant have currently?',
|
|
2603
|
-
collectionHints: ['chemical-inventory-snapshots', 'inventory-reports'],
|
|
2604
|
-
collectionNames: ['chemical-inventory-snapshots', 'inventory-reports'],
|
|
2605
|
-
reason: 'Expected shared library to avoid emitting inventory quantity directives'
|
|
2606
|
-
},
|
|
2607
|
-
{
|
|
2608
|
-
message: 'Summarize the work orders this week by customer.',
|
|
2609
|
-
collectionHints: ['work-order-dynamics', 'orders'],
|
|
2610
|
-
collectionNames: ['work-order-dynamics', 'orders'],
|
|
2611
|
-
reason: 'Expected shared library to avoid emitting work-order customer breakdown directives'
|
|
2612
|
-
},
|
|
2613
|
-
{
|
|
2614
|
-
message: 'Show work orders created this week by status.',
|
|
2615
|
-
collectionHints: ['work-order-dynamics', 'orders'],
|
|
2616
|
-
collectionNames: ['work-order-dynamics', 'orders'],
|
|
2617
|
-
reason: 'Expected shared library to avoid emitting work-order status directives'
|
|
2618
|
-
},
|
|
2619
|
-
{
|
|
2620
|
-
message: 'Show completed work orders by day for the last 30 days.',
|
|
2621
|
-
collectionHints: ['work-order-dynamics', 'orders'],
|
|
2622
|
-
collectionNames: ['work-order-dynamics', 'orders'],
|
|
2623
|
-
reason: 'Expected shared library to avoid emitting completed work-order per-day directives'
|
|
2624
|
-
},
|
|
2625
|
-
{
|
|
2626
|
-
message: 'Break down the last 20 completed work orders and tell me what customer, what product and how much we delivered or returned.',
|
|
2627
|
-
collectionHints: ['work-order-dynamics', 'orders'],
|
|
2628
|
-
collectionNames: ['work-order-dynamics', 'orders'],
|
|
2629
|
-
reason: 'Expected shared library to avoid emitting work-order delivery breakdown directives'
|
|
2630
|
-
},
|
|
2631
|
-
{
|
|
2632
|
-
message: 'Show the top 10 customers by number of work orders in the last 6 months.',
|
|
2633
|
-
collectionHints: ['work-order-dynamics', 'orders'],
|
|
2634
|
-
collectionNames: ['work-order-dynamics', 'orders'],
|
|
2635
|
-
reason: 'Expected shared library to avoid emitting work-order ranking directives'
|
|
2636
|
-
},
|
|
2637
|
-
{
|
|
2638
|
-
message: 'Show deliveries per driver last month.',
|
|
2639
|
-
collectionHints: ['work-order-dynamics', 'orders'],
|
|
2640
|
-
collectionNames: ['work-order-dynamics', 'orders'],
|
|
2641
|
-
reason: 'Expected shared library to avoid emitting delivery ranking directives'
|
|
2642
|
-
},
|
|
2643
|
-
{
|
|
2644
|
-
message: 'How many active jobs are there?',
|
|
2645
|
-
collectionHints: ['jobs'],
|
|
2646
|
-
collectionNames: ['jobs'],
|
|
2647
|
-
reason: 'Expected shared library to avoid emitting active jobs directives'
|
|
2648
|
-
},
|
|
2649
|
-
{
|
|
2650
|
-
message: 'Show the top 10 customers by revenue by division in the last 6 months.',
|
|
2651
|
-
collectionHints: ['invoices'],
|
|
2652
|
-
collectionNames: ['invoices'],
|
|
2653
|
-
reason: 'Expected shared library to avoid emitting revenue ranking directives'
|
|
2654
|
-
}
|
|
2655
|
-
];
|
|
2656
|
-
cases.forEach((testCase) => {
|
|
2657
|
-
assert(
|
|
2658
|
-
!resolveAssistantHeuristicDirectiveForTesting(
|
|
2659
|
-
testCase.message,
|
|
2660
|
-
testCase.collectionHints,
|
|
2661
|
-
testCase.collectionNames,
|
|
2662
|
-
'snf'
|
|
2663
|
-
),
|
|
2664
|
-
testCase.reason
|
|
2665
|
-
);
|
|
2666
|
-
});
|
|
2667
|
-
}
|
|
2668
|
-
finally {
|
|
2669
|
-
if (priorInline === undefined) {
|
|
2670
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
2671
|
-
}
|
|
2672
|
-
else {
|
|
2673
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = priorInline;
|
|
2674
|
-
}
|
|
2675
|
-
if (priorFile === undefined) {
|
|
2676
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
2677
|
-
}
|
|
2678
|
-
else {
|
|
2679
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = priorFile;
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
|
-
}
|
|
2683
|
-
|
|
2684
|
-
function testAssistantHeuristicBlendIdsRemainAppSpecific() {
|
|
2685
|
-
const priorInline = process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
2686
|
-
const priorFile = process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
2687
|
-
try {
|
|
2688
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = '';
|
|
2689
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = JSON.stringify({
|
|
2690
|
-
apps: {
|
|
2691
|
-
snf: {
|
|
2692
|
-
enabled_deterministic_heuristics: [
|
|
2693
|
-
'blend_ticket_summary',
|
|
2694
|
-
'blend_throughput_day_window',
|
|
2695
|
-
'blend_user_window_count'
|
|
2696
|
-
]
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
});
|
|
2700
|
-
const summaryDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2701
|
-
'For blending: summarize the last 10 blend tickets with product, total volume, and created date.',
|
|
2702
|
-
['chemical-blends'],
|
|
2703
|
-
['chemical-blends', 'report-chemical-blends'],
|
|
2704
|
-
'snf'
|
|
2705
|
-
);
|
|
2706
|
-
assert(
|
|
2707
|
-
!summaryDirective,
|
|
2708
|
-
'Expected shared library to avoid emitting app-specific blend ticket summary directives'
|
|
2709
|
-
);
|
|
2710
|
-
const throughputDirective = resolveAssistantHeuristicDirectiveForTesting(
|
|
2711
|
-
'Show blend throughput by day for the last 2 weeks.',
|
|
2712
|
-
['chemical-blends'],
|
|
2713
|
-
['chemical-blends', 'report-chemical-blends'],
|
|
2714
|
-
'snf'
|
|
2715
|
-
);
|
|
2716
|
-
assert(
|
|
2717
|
-
!throughputDirective,
|
|
2718
|
-
'Expected shared library to avoid emitting app-specific blend throughput directives'
|
|
2719
|
-
);
|
|
2720
|
-
assert(
|
|
2721
|
-
!resolveAssistantHeuristicDirectiveForTesting(
|
|
2722
|
-
'How many blends has John Doe completed in the last 30 days?',
|
|
2723
|
-
['chemical-blends'],
|
|
2724
|
-
['chemical-blends', 'report-chemical-blends'],
|
|
2725
|
-
'snf'
|
|
2726
|
-
),
|
|
2727
|
-
'Expected shared library to avoid emitting app-specific blend user-window directives'
|
|
2728
|
-
);
|
|
2729
|
-
}
|
|
2730
|
-
finally {
|
|
2731
|
-
if (priorInline === undefined) {
|
|
2732
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS;
|
|
2733
|
-
}
|
|
2734
|
-
else {
|
|
2735
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS = priorInline;
|
|
2736
|
-
}
|
|
2737
|
-
if (priorFile === undefined) {
|
|
2738
|
-
delete process.env.AI_ASSISTANT_APP_HEURISTICS_FILE;
|
|
2739
|
-
}
|
|
2740
|
-
else {
|
|
2741
|
-
process.env.AI_ASSISTANT_APP_HEURISTICS_FILE = priorFile;
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
|
|
2746
|
-
function testShouldEnforceAssistantDatedDirectiveSkipsWindowedRankingRequests() {
|
|
2747
|
-
const classification = { type: 'data', dataQuestion: true, source: 'heuristic' } as any;
|
|
2748
|
-
const shouldEnforce = shouldEnforceAssistantDatedDirective(
|
|
2749
|
-
'List the top 10 customers by number of work orders in the last 6 months.',
|
|
2750
|
-
{},
|
|
2751
|
-
classification
|
|
2752
|
-
);
|
|
2753
|
-
assert(!shouldEnforce, 'Expected windowed ranking request to avoid forced dated grouping');
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
|
-
function testShouldEnforceAssistantDatedDirectiveForExplicitTemporalBreakdown() {
|
|
2757
|
-
const classification = { type: 'data', dataQuestion: true, source: 'heuristic' } as any;
|
|
2758
|
-
const shouldEnforce = shouldEnforceAssistantDatedDirective(
|
|
2759
|
-
'Break down total revenue over the last 6 months by month.',
|
|
2760
|
-
{},
|
|
2761
|
-
classification
|
|
2762
|
-
);
|
|
2763
|
-
assert(shouldEnforce, 'Expected explicit by-month request to enforce dated grouping');
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2766
|
-
function testShouldEnforceAssistantDatedDirectiveForPlannerTemporalGroupBy() {
|
|
2767
|
-
const classification = { type: 'data', dataQuestion: true, source: 'planner' } as any;
|
|
2768
|
-
const shouldEnforce = shouldEnforceAssistantDatedDirective(
|
|
2769
|
-
'Show revenue for the last 6 months.',
|
|
2770
|
-
{ dataPlan: { queryPlan: { groupBy: 'month' } } },
|
|
2771
|
-
classification
|
|
2772
|
-
);
|
|
2773
|
-
assert(shouldEnforce, 'Expected planner temporal groupBy to enforce dated grouping');
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
|
-
function testBuildAssistantDatedPivotDisplayWithYearMonthAndCustomer() {
|
|
2777
|
-
const display = {
|
|
2778
|
-
columns: ['Customer', 'Year', 'Month', 'Total Revenue'],
|
|
2779
|
-
rows: [
|
|
2780
|
-
{ Customer: 'Apache', Year: 2025, Month: 11, 'Total Revenue': 1000 },
|
|
2781
|
-
{ Customer: 'Apache', Year: 2025, Month: 12, 'Total Revenue': 2000 },
|
|
2782
|
-
{ Customer: 'Pioneer', Year: 2025, Month: 11, 'Total Revenue': 1500 },
|
|
2783
|
-
{ Customer: 'Pioneer', Year: 2025, Month: 12, 'Total Revenue': 500 }
|
|
2784
|
-
],
|
|
2785
|
-
rowCount: 4,
|
|
2786
|
-
truncated: false,
|
|
2787
|
-
includeIds: false
|
|
2788
|
-
};
|
|
2789
|
-
const pivoted = buildAssistantDatedPivotDisplay(display as any);
|
|
2790
|
-
assert(!!pivoted, 'Expected dated pivot display to be produced');
|
|
2791
|
-
assert(
|
|
2792
|
-
pivoted?.columns?.[0] === 'Customer',
|
|
2793
|
-
'Expected customer to remain first pivot column'
|
|
2794
|
-
);
|
|
2795
|
-
assert(
|
|
2796
|
-
pivoted?.columns?.includes('2025-11') && pivoted?.columns?.includes('2025-12'),
|
|
2797
|
-
'Expected YYYY-MM month columns'
|
|
2798
|
-
);
|
|
2799
|
-
assert(
|
|
2800
|
-
(pivoted?.columns || []).indexOf('2025-12') < (pivoted?.columns || []).indexOf('2025-11'),
|
|
2801
|
-
'Expected dated pivot month columns to show newest month first'
|
|
2802
|
-
);
|
|
2803
|
-
const apacheRow = (pivoted?.rows || []).find((row: any) => row?.Customer === 'Apache');
|
|
2804
|
-
assert(!!apacheRow, 'Expected Apache row in pivot');
|
|
2805
|
-
assert(
|
|
2806
|
-
String(apacheRow?.['2025-11'] || '').includes('$'),
|
|
2807
|
-
'Expected revenue cells to be currency formatted'
|
|
2808
|
-
);
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
function testBuildAssistantDatedPivotDisplayWithoutDimensionCreatesMetricGrid() {
|
|
2812
|
-
const display = {
|
|
2813
|
-
columns: ['Year', 'Month', 'Total Revenue'],
|
|
2814
|
-
rows: [
|
|
2815
|
-
{ Year: 2025, Month: 11, 'Total Revenue': 3200 },
|
|
2816
|
-
{ Year: 2025, Month: 12, 'Total Revenue': 4100 },
|
|
2817
|
-
{ Year: 2026, Month: 1, 'Total Revenue': 5050 }
|
|
2818
|
-
],
|
|
2819
|
-
rowCount: 3,
|
|
2820
|
-
truncated: false,
|
|
2821
|
-
includeIds: false
|
|
2822
|
-
};
|
|
2823
|
-
const pivoted = buildAssistantDatedPivotDisplay(display as any);
|
|
2824
|
-
assert(!!pivoted, 'Expected dated pivot grid even without explicit dimension');
|
|
2825
|
-
assert(pivoted?.columns?.[0] === 'Metric', 'Expected synthetic Metric column');
|
|
2826
|
-
assert(
|
|
2827
|
-
pivoted?.columns?.includes('2025-11')
|
|
2828
|
-
&& pivoted?.columns?.includes('2025-12')
|
|
2829
|
-
&& pivoted?.columns?.includes('2026-01'),
|
|
2830
|
-
'Expected month header columns for all buckets'
|
|
2831
|
-
);
|
|
2832
|
-
assert(
|
|
2833
|
-
(pivoted?.columns || []).indexOf('2026-01') < (pivoted?.columns || []).indexOf('2025-12'),
|
|
2834
|
-
'Expected metric pivot month columns to show newest month first'
|
|
2835
|
-
);
|
|
2836
|
-
assert((pivoted?.rows || []).length === 1, 'Expected single metric row for monthly totals');
|
|
2837
|
-
assert(
|
|
2838
|
-
normalizeCellText((pivoted?.rows || [])[0]?.Metric) === 'Total Revenue',
|
|
2839
|
-
'Expected metric row label to use value column'
|
|
2840
|
-
);
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2843
|
-
function testBuildAssistantInvoiceCustomerLabelExprIncludesClientFallbacks() {
|
|
2844
|
-
const expression = buildAssistantInvoiceCustomerLabelExpr();
|
|
2845
|
-
const serialized = JSON.stringify(expression);
|
|
2846
|
-
const requiredTokens = [
|
|
2847
|
-
'$customer_name',
|
|
2848
|
-
'$customer.fullname',
|
|
2849
|
-
'$customer.name',
|
|
2850
|
-
'$customer',
|
|
2851
|
-
'$client_name',
|
|
2852
|
-
'$client.fullname',
|
|
2853
|
-
'$client.name',
|
|
2854
|
-
'$client',
|
|
2855
|
-
'$id_customer',
|
|
2856
|
-
'$id_client',
|
|
2857
|
-
'Unknown Customer',
|
|
2858
|
-
'$strLenCP'
|
|
2859
|
-
];
|
|
2860
|
-
requiredTokens.forEach((token) => {
|
|
2861
|
-
assert(
|
|
2862
|
-
serialized.includes(token),
|
|
2863
|
-
`Expected invoice customer expression to include ${token}`
|
|
2864
|
-
);
|
|
2865
|
-
});
|
|
2866
|
-
}
|
|
2867
|
-
|
|
2868
|
-
function testResolveAssistantReadDisplayMaxRows() {
|
|
2869
|
-
assert(
|
|
2870
|
-
resolveAssistantReadDisplayMaxRows(0, 92) === 20,
|
|
2871
|
-
'Expected limit=0 to fallback to preview rows instead of zero rows'
|
|
2872
|
-
);
|
|
2873
|
-
assert(
|
|
2874
|
-
resolveAssistantReadDisplayMaxRows(undefined, 7) === 7,
|
|
2875
|
-
'Expected small row counts to be preserved for preview'
|
|
2876
|
-
);
|
|
2877
|
-
assert(
|
|
2878
|
-
resolveAssistantReadDisplayMaxRows(5, 92) === 5,
|
|
2879
|
-
'Expected explicit positive limit to be respected'
|
|
2880
|
-
);
|
|
2881
|
-
assert(
|
|
2882
|
-
resolveAssistantReadDisplayMaxRows(undefined, 0) === 20,
|
|
2883
|
-
'Expected empty reads to keep default preview row ceiling'
|
|
2884
|
-
);
|
|
2885
|
-
}
|
|
2886
|
-
|
|
2887
|
-
function testResolveAssistantCollectionOverrideKeepsSpecificOrdersCollection() {
|
|
2888
|
-
const override = resolveAssistantCollectionOverride(
|
|
2889
|
-
{
|
|
2890
|
-
ranked: [
|
|
2891
|
-
{ name: 'orders', score: 40 },
|
|
2892
|
-
{ name: 'maintenance-orders', score: 20 }
|
|
2893
|
-
]
|
|
2894
|
-
},
|
|
2895
|
-
'maintenance-orders'
|
|
2896
|
-
);
|
|
2897
|
-
assert(!override, 'Expected maintenance-orders to not be overridden by generic orders');
|
|
2898
|
-
}
|
|
2899
|
-
|
|
2900
|
-
function testResolveAssistantCollectionOverrideStillAppliesForUnrelatedCollection() {
|
|
2901
|
-
const override = resolveAssistantCollectionOverride(
|
|
2902
|
-
{
|
|
2903
|
-
ranked: [
|
|
2904
|
-
{ name: 'orders', score: 40 },
|
|
2905
|
-
{ name: 'maintenance-orders', score: 20 }
|
|
2906
|
-
]
|
|
2907
|
-
},
|
|
2908
|
-
'clients'
|
|
2909
|
-
);
|
|
2910
|
-
assert(!!override, 'Expected unrelated collection to be overridden by highest-ranked candidate');
|
|
2911
|
-
assert(override?.to === 'orders', 'Expected override target to be orders');
|
|
2912
|
-
}
|
|
2913
|
-
|
|
2914
|
-
function testResolveAssistantCollectionOverrideSkipsBorderlineGapForWorkOrderRanking() {
|
|
2915
|
-
const override = resolveAssistantCollectionOverride(
|
|
2916
|
-
{
|
|
2917
|
-
tokens: ['top', '10', 'customers', 'customer', 'number', 'work', 'orders', 'order', '6', 'months'],
|
|
2918
|
-
tokenWeights: {
|
|
2919
|
-
top: 1,
|
|
2920
|
-
'10': 1,
|
|
2921
|
-
customers: 1,
|
|
2922
|
-
customer: 1,
|
|
2923
|
-
number: 1,
|
|
2924
|
-
work: 1,
|
|
2925
|
-
orders: 1,
|
|
2926
|
-
order: 1,
|
|
2927
|
-
'6': 1,
|
|
2928
|
-
months: 1
|
|
2929
|
-
},
|
|
2930
|
-
ranked: [
|
|
2931
|
-
{ name: 'customers', score: 40 },
|
|
2932
|
-
{ name: 'orders', score: 40 }
|
|
2933
|
-
]
|
|
2934
|
-
},
|
|
2935
|
-
'work-order-dynamics'
|
|
2936
|
-
);
|
|
2937
|
-
assert(!override, 'Expected work-order-dynamics to be preserved when score gap is only at threshold');
|
|
2938
|
-
}
|
|
2939
|
-
|
|
2940
|
-
function testResolveAssistantCollectionOverrideKeepsWorkOrderCollectionWhenOrdersScoresHigher() {
|
|
2941
|
-
const override = resolveAssistantCollectionOverride(
|
|
2942
|
-
{
|
|
2943
|
-
ranked: [
|
|
2944
|
-
{ name: 'orders', score: 85 },
|
|
2945
|
-
{ name: 'work-order-dynamics', score: 64 }
|
|
2946
|
-
]
|
|
2947
|
-
},
|
|
2948
|
-
'work-order-dynamics'
|
|
2949
|
-
);
|
|
2950
|
-
assert(!override, 'Expected work-order-dynamics to stay selected over generic orders');
|
|
2951
|
-
}
|
|
2952
|
-
|
|
2953
|
-
function testResolveCollectionOverrideWithContextRespectsPermissionRouteHints() {
|
|
2954
|
-
const override = resolveCollectionOverrideWithContextForTesting({
|
|
2955
|
-
message: 'How many pods do we have in inventory?',
|
|
2956
|
-
collectionRanking: {
|
|
2957
|
-
ranked: [
|
|
2958
|
-
{ name: 'inventory-report-text-users', score: 39 },
|
|
2959
|
-
{ name: 'assets', score: 0 }
|
|
2960
|
-
]
|
|
2961
|
-
},
|
|
2962
|
-
requestedCollection: 'assets',
|
|
2963
|
-
permissionView: '/asset/list',
|
|
2964
|
-
collectionNames: ['assets', 'inventory-report-text-users']
|
|
2965
|
-
});
|
|
2966
|
-
assert(!override, 'Expected /asset/list route hints to keep assets collection');
|
|
2967
|
-
}
|
|
2968
|
-
|
|
2969
|
-
function testResolveCollectionOverrideWithContextUsesSpecificPermissionRouteCollection() {
|
|
2970
|
-
const override = resolveCollectionOverrideWithContextForTesting({
|
|
2971
|
-
message: 'Summarize the work orders this week by customer.',
|
|
2972
|
-
collectionRanking: {
|
|
2973
|
-
ranked: [
|
|
2974
|
-
{ name: 'production-sales-orders', score: 80 },
|
|
2975
|
-
{ name: 'work-order-dynamics', score: 70 }
|
|
2976
|
-
]
|
|
2977
|
-
},
|
|
2978
|
-
requestedCollection: 'production-sales-orders',
|
|
2979
|
-
permissionView: '/work-order-dynamic/list',
|
|
2980
|
-
collectionNames: ['production-sales-orders', 'work-order-dynamics']
|
|
2981
|
-
});
|
|
2982
|
-
assert(!!override, 'Expected work-order permission route to correct mismatched production-sales-orders collection');
|
|
2983
|
-
assert(
|
|
2984
|
-
override?.to === 'work-order-dynamics',
|
|
2985
|
-
'Expected override target to be work-order-dynamics'
|
|
2986
|
-
);
|
|
2987
|
-
}
|
|
2988
|
-
|
|
2989
|
-
function testResolveCollectionOverrideWithContextDoesNotRewriteAggregateDataPlanToRouteCollection() {
|
|
2990
|
-
const aggregateOverride = resolveCollectionOverrideWithContextForTesting({
|
|
2991
|
-
message: 'summarize all the activity for 26% nacl brine this current year',
|
|
2992
|
-
collectionRanking: {
|
|
2993
|
-
ranked: [
|
|
2994
|
-
{ name: 'chemicals', score: 88 },
|
|
2995
|
-
{ name: 'chemical-transactions', score: 84 }
|
|
2996
|
-
]
|
|
2997
|
-
},
|
|
2998
|
-
requestedCollection: 'chemical-transactions',
|
|
2999
|
-
permissionView: '/chemical/detail',
|
|
3000
|
-
collectionNames: ['chemicals', 'chemical-transactions'],
|
|
3001
|
-
collectionHints: ['chemicals', 'chemical-transactions'],
|
|
3002
|
-
directiveType: 'aggregate'
|
|
3003
|
-
});
|
|
3004
|
-
assert(!aggregateOverride, 'Expected aggregate chemical activity plan to keep chemical-transactions instead of the route chemicals collection');
|
|
3005
|
-
|
|
3006
|
-
const readOverride = resolveCollectionOverrideWithContextForTesting({
|
|
3007
|
-
message: 'open this chemical record',
|
|
3008
|
-
collectionRanking: {
|
|
3009
|
-
ranked: [
|
|
3010
|
-
{ name: 'chemicals', score: 88 },
|
|
3011
|
-
{ name: 'chemical-transactions', score: 84 }
|
|
3012
|
-
]
|
|
3013
|
-
},
|
|
3014
|
-
requestedCollection: 'chemical-transactions',
|
|
3015
|
-
permissionView: '/chemical/detail',
|
|
3016
|
-
collectionNames: ['chemicals', 'chemical-transactions'],
|
|
3017
|
-
collectionHints: ['chemicals', 'chemical-transactions'],
|
|
3018
|
-
directiveType: 'read'
|
|
3019
|
-
});
|
|
3020
|
-
assert(!!readOverride, 'Expected non-aggregate route-context collection correction to remain available');
|
|
3021
|
-
assert(readOverride?.to === 'chemicals', 'Expected non-aggregate route correction to target chemicals');
|
|
3022
|
-
}
|
|
3023
|
-
|
|
3024
|
-
function testResolveCollectionOverrideWithContextUsesOrderedCollectionHints() {
|
|
3025
|
-
const override = resolveCollectionOverrideWithContextForTesting({
|
|
3026
|
-
message: 'Summarize the work orders this week by customer.',
|
|
3027
|
-
collectionRanking: {
|
|
3028
|
-
ranked: [
|
|
3029
|
-
{ name: 'customers', score: 90 },
|
|
3030
|
-
{ name: 'production-sales-orders', score: 80 }
|
|
3031
|
-
]
|
|
3032
|
-
},
|
|
3033
|
-
requestedCollection: 'customers',
|
|
3034
|
-
permissionView: '/work-order-dynamic/list',
|
|
3035
|
-
collectionNames: ['customers', 'production-sales-orders'],
|
|
3036
|
-
collectionHints: ['work-order-dynamics', 'jobs', 'maintenance-orders', 'customers', 'orders']
|
|
3037
|
-
});
|
|
3038
|
-
assert(!!override, 'Expected ordered collection hints to correct grouped-dimension collection selection');
|
|
3039
|
-
assert(
|
|
3040
|
-
override?.to === 'work-order-dynamics',
|
|
3041
|
-
'Expected override target to be the first ordered collection hint'
|
|
3042
|
-
);
|
|
3043
|
-
}
|
|
3044
|
-
|
|
3045
|
-
function testResolveCollectionOverrideWithContextPreservesPrimaryCollectionHint() {
|
|
3046
|
-
const override = resolveCollectionOverrideWithContextForTesting({
|
|
3047
|
-
message: 'Summarize the work orders this week by customer.',
|
|
3048
|
-
collectionRanking: {
|
|
3049
|
-
ranked: [
|
|
3050
|
-
{ name: 'customer-portal-passwords', score: 45 },
|
|
3051
|
-
{ name: 'work-order-dynamics', score: 20 }
|
|
3052
|
-
]
|
|
3053
|
-
},
|
|
3054
|
-
requestedCollection: 'work-order-dynamics',
|
|
3055
|
-
permissionView: '/work-order-dynamic/list',
|
|
3056
|
-
collectionNames: ['customer-portal-passwords', 'work-order-dynamics'],
|
|
3057
|
-
collectionHints: ['work-order-dynamics', 'jobs', 'maintenance-orders', 'customers', 'orders']
|
|
3058
|
-
});
|
|
3059
|
-
assert(!override, 'Expected primary collection hint to preserve the model-selected collection');
|
|
3060
|
-
}
|
|
3061
|
-
|
|
3062
|
-
function testResolveCollectionOverrideWithContextKeepsRouteMatchedJobsOverChemicalHint() {
|
|
3063
|
-
const override = resolveCollectionOverrideWithContextForTesting({
|
|
3064
|
-
message: 'Summarize Devon Energy jobs and amount of chemicals sent to them for this year',
|
|
3065
|
-
collectionRanking: {
|
|
3066
|
-
ranked: [
|
|
3067
|
-
{ name: 'chemicals', score: 79 },
|
|
3068
|
-
{ name: 'jobs', score: 76 },
|
|
3069
|
-
{ name: 'work-order-dynamics', score: 38 }
|
|
3070
|
-
]
|
|
3071
|
-
},
|
|
3072
|
-
requestedCollection: 'jobs',
|
|
3073
|
-
permissionView: '/job/list',
|
|
3074
|
-
collectionNames: ['chemicals', 'jobs', 'work-order-dynamics'],
|
|
3075
|
-
collectionHints: ['chemicals', 'jobs', 'work-order-dynamics', 'chemical-blends']
|
|
3076
|
-
});
|
|
3077
|
-
assert(!override, 'Expected /job/list route to preserve model-selected jobs over a chemical keyword hint');
|
|
3078
|
-
}
|
|
3079
|
-
|
|
3080
|
-
function testResolveCollectionOverrideWithContextPreservesJobDomainDetailCollections() {
|
|
3081
|
-
const override = resolveCollectionOverrideWithContextForTesting({
|
|
3082
|
-
message: 'Summarize Devon Energy jobs and amount of chemicals sent to them for this year',
|
|
3083
|
-
collectionRanking: {
|
|
3084
|
-
ranked: [
|
|
3085
|
-
{ name: 'work-order-dynamics', score: 80 },
|
|
3086
|
-
{ name: 'jobs', score: 76 }
|
|
3087
|
-
]
|
|
3088
|
-
},
|
|
3089
|
-
requestedCollection: 'work-order-dynamics',
|
|
3090
|
-
permissionView: '/customer-info/jobs',
|
|
3091
|
-
collectionNames: ['jobs', 'work-order-dynamics', 'invoices'],
|
|
3092
|
-
collectionHints: ['work-order-dynamics', 'jobs', 'invoices'],
|
|
3093
|
-
collectionPreserveRules: [
|
|
3094
|
-
{
|
|
3095
|
-
routePreferredCollections: ['jobs'],
|
|
3096
|
-
routePatterns: ['/customer-info/jobs', '/job/list', '/jobs'],
|
|
3097
|
-
requestedCollections: ['work-order-dynamics'],
|
|
3098
|
-
contextTerms: ['job', 'jobs', 'work order', 'work orders'],
|
|
3099
|
-
terms: ['chemical', 'chemicals', 'sent', 'shipped']
|
|
3100
|
-
}
|
|
3101
|
-
]
|
|
3102
|
-
});
|
|
3103
|
-
assert(!override, 'Expected job route to preserve work-order-dynamics for shipped chemical job summaries');
|
|
3104
|
-
}
|
|
3105
|
-
|
|
3106
|
-
function testResolveCollectionOverrideWithContextKeepsCompetitiveRootCollectionHint() {
|
|
3107
|
-
const override = resolveCollectionOverrideWithContextForTesting({
|
|
3108
|
-
message: 'Summarize Devon Energy jobs and amount of chemicals sent to them for this year',
|
|
3109
|
-
collectionRanking: {
|
|
3110
|
-
ranked: [
|
|
3111
|
-
{ name: 'chemicals', score: 79 },
|
|
3112
|
-
{ name: 'jobs', score: 76 },
|
|
3113
|
-
{ name: 'work-order-dynamics', score: 38 }
|
|
3114
|
-
]
|
|
3115
|
-
},
|
|
3116
|
-
requestedCollection: 'jobs',
|
|
3117
|
-
collectionNames: ['chemicals', 'jobs', 'work-order-dynamics'],
|
|
3118
|
-
collectionHints: ['chemicals', 'jobs', 'work-order-dynamics', 'chemical-blends']
|
|
3119
|
-
});
|
|
3120
|
-
assert(!override, 'Expected close-ranked model-selected root collection to survive a related detail collection hint');
|
|
3121
|
-
}
|
|
3122
|
-
|
|
3123
|
-
function testShouldPreserveAssistantProbeCollectionForPrimaryHintAndRoute() {
|
|
3124
|
-
const directive = {
|
|
3125
|
-
type: 'aggregate',
|
|
3126
|
-
payload: {
|
|
3127
|
-
collection: 'work-order-dynamics',
|
|
3128
|
-
permissionView: '/work-order-dynamic/list'
|
|
3129
|
-
}
|
|
3130
|
-
};
|
|
3131
|
-
const preserveFromHints = shouldPreserveAssistantProbeCollectionForTesting(
|
|
3132
|
-
directive,
|
|
3133
|
-
'production-sales-orders',
|
|
3134
|
-
{
|
|
3135
|
-
debug: {
|
|
3136
|
-
requestHints: {
|
|
3137
|
-
collectionHints: ['work-order-dynamics', 'production-sales-orders']
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
);
|
|
3142
|
-
assert(preserveFromHints, 'Expected probe rewrite to preserve primary hinted work-order collection');
|
|
3143
|
-
|
|
3144
|
-
const preserveFromRoute = shouldPreserveAssistantProbeCollectionForTesting(
|
|
3145
|
-
directive,
|
|
3146
|
-
'production-sales-orders'
|
|
3147
|
-
);
|
|
3148
|
-
assert(preserveFromRoute, 'Expected probe rewrite to preserve collection matching the permission route');
|
|
3149
|
-
|
|
3150
|
-
const lockForExecution = shouldLockAssistantRequestedCollectionForExecutionForTesting(
|
|
3151
|
-
'work-order-dynamics',
|
|
3152
|
-
'/work-order-dynamic',
|
|
3153
|
-
{
|
|
3154
|
-
collectionHints: ['work-order-dynamics', 'production-sales-orders']
|
|
3155
|
-
}
|
|
3156
|
-
);
|
|
3157
|
-
assert(lockForExecution, 'Expected execution to disable cross-collection retry for route/primary hinted collection');
|
|
3158
|
-
}
|
|
3159
|
-
|
|
3160
|
-
function testResolveCollectionOverrideWithContextStillOverridesWithoutRouteHints() {
|
|
3161
|
-
const override = resolveCollectionOverrideWithContextForTesting({
|
|
3162
|
-
message: 'How many pods do we have in inventory?',
|
|
3163
|
-
collectionRanking: {
|
|
3164
|
-
ranked: [
|
|
3165
|
-
{ name: 'inventory-report-text-users', score: 39 },
|
|
3166
|
-
{ name: 'assets', score: 0 }
|
|
3167
|
-
]
|
|
3168
|
-
},
|
|
3169
|
-
requestedCollection: 'assets',
|
|
3170
|
-
permissionView: '/dashboard/home',
|
|
3171
|
-
collectionNames: ['assets', 'inventory-report-text-users']
|
|
3172
|
-
});
|
|
3173
|
-
assert(!!override, 'Expected override to still apply when route has no collection hints');
|
|
3174
|
-
assert(
|
|
3175
|
-
override?.to === 'inventory-report-text-users',
|
|
3176
|
-
'Expected override target to remain the top ranked collection when route hints are absent'
|
|
3177
|
-
);
|
|
3178
|
-
}
|
|
3179
|
-
|
|
3180
|
-
function testResolveAssistantCrossCollectionFallbackCandidatesForOrders() {
|
|
3181
|
-
const candidates = resolveAssistantCrossCollectionFallbackCandidates('orders');
|
|
3182
|
-
assert(candidates.length === 0, 'Expected non-aliased collections to have no hardcoded fallback list');
|
|
3183
|
-
}
|
|
3184
|
-
|
|
3185
|
-
function testResolveAssistantCrossCollectionFallbackCandidatesForWorkOrderDynamics() {
|
|
3186
|
-
const candidates = resolveAssistantCrossCollectionFallbackCandidates('work-order-dynamics');
|
|
3187
|
-
assert(candidates.length === 0, 'Expected work-order-dynamics to have no hardcoded fallback list');
|
|
3188
|
-
}
|
|
3189
|
-
|
|
3190
|
-
function testResolveAssistantCrossCollectionFallbackCandidatesForUnknownCollection() {
|
|
3191
|
-
const candidates = resolveAssistantCrossCollectionFallbackCandidates('foobar-collection');
|
|
3192
|
-
assert(candidates.length === 0, 'Expected unknown collection fallback candidates to be empty');
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
function testResolveAssistantCrossCollectionFallbackCandidatesForTimeEntries() {
|
|
3196
|
-
const candidates = resolveAssistantCrossCollectionFallbackCandidates('time_entries');
|
|
3197
|
-
assert(candidates.length === 0, 'Expected time entries to have no hardcoded fallback list');
|
|
3198
|
-
}
|
|
3199
|
-
|
|
3200
|
-
function testResolveAssistantAvailableCrossCollectionFallbacksFromNamesUsesSimilarityRanking() {
|
|
3201
|
-
const available = resolveAssistantAvailableCrossCollectionFallbacksFromNames(
|
|
3202
|
-
'orders',
|
|
3203
|
-
new Set<string>(['orders']),
|
|
3204
|
-
['orders', 'maintenance-orders', 'repair-orders', 'customers']
|
|
3205
|
-
);
|
|
3206
|
-
assert(
|
|
3207
|
-
available.includes('maintenance-orders') || available.includes('repair-orders'),
|
|
3208
|
-
'Expected orders fallback to include related collections from token similarity'
|
|
3209
|
-
);
|
|
3210
|
-
}
|
|
3211
|
-
|
|
3212
|
-
function testResolveAssistantAvailableCrossCollectionFallbacksFromNamesSkipsCurrentFamilyOnlyMatch() {
|
|
3213
|
-
const available = resolveAssistantAvailableCrossCollectionFallbacksFromNames(
|
|
3214
|
-
'time_entries',
|
|
3215
|
-
new Set<string>(['time_entries']),
|
|
3216
|
-
['time_entries']
|
|
3217
|
-
);
|
|
3218
|
-
assert(
|
|
3219
|
-
available.length === 0,
|
|
3220
|
-
'Expected no fallback when only the current collection family is available'
|
|
3221
|
-
);
|
|
3222
|
-
}
|
|
3223
|
-
|
|
3224
|
-
function testResolveAssistantCrossCollectionFallbackCandidatesForSupportAlias() {
|
|
3225
|
-
const candidates = resolveAssistantCrossCollectionFallbackCandidates('supporttickets');
|
|
3226
|
-
assert(
|
|
3227
|
-
candidates.length === 0,
|
|
3228
|
-
'Expected shared library to avoid injecting support ticket fallback aliases'
|
|
3229
|
-
);
|
|
3230
|
-
}
|
|
3231
|
-
|
|
3232
|
-
function testShouldAcceptAssistantFallbackDocumentsRejectsBlankDimensionRows() {
|
|
3233
|
-
const accepted = shouldAcceptAssistantFallbackDocuments([
|
|
3234
|
-
{ 'Work Orders': 23, Customer: '' }
|
|
3235
|
-
]);
|
|
3236
|
-
assert(!accepted, 'Expected fallback documents with empty dimensions to be rejected');
|
|
3237
|
-
}
|
|
3238
|
-
|
|
3239
|
-
function testShouldAcceptAssistantFallbackDocumentsAcceptsNamedDimensions() {
|
|
3240
|
-
const accepted = shouldAcceptAssistantFallbackDocuments([
|
|
3241
|
-
{ 'Work Orders': 23, Customer: 'Acme Services' }
|
|
3242
|
-
]);
|
|
3243
|
-
assert(accepted, 'Expected fallback documents with populated dimensions to be accepted');
|
|
3244
|
-
}
|
|
3245
|
-
|
|
3246
|
-
function testShouldAcceptAssistantFallbackDocumentsAcceptsMetricOnlyRows() {
|
|
3247
|
-
const accepted = shouldAcceptAssistantFallbackDocuments([
|
|
3248
|
-
{ Count: 42, Total: 4200 }
|
|
3249
|
-
]);
|
|
3250
|
-
assert(accepted, 'Expected metric-only fallback documents to be accepted');
|
|
3251
|
-
}
|
|
3252
|
-
|
|
3253
|
-
function normalizeCellText(value: any): string {
|
|
3254
|
-
return String(value || '').trim();
|
|
3255
|
-
}
|
|
3256
|
-
|
|
3257
|
-
function testDisplayMarkdownInvoiceNumbersAndCountsStayNumeric() {
|
|
3258
|
-
const display = buildDisplayTable([
|
|
3259
|
-
{ invoice_number: 0, total: 558, voidedCount: 558 },
|
|
3260
|
-
{ invoice_number: 203746, total: 2, voidedCount: 2 }
|
|
3261
|
-
]);
|
|
3262
|
-
const markdown = formatDisplayTableMarkdown(display);
|
|
3263
|
-
assert(!markdown.includes('$0.00'), 'Expected invoice number 0 to remain numeric');
|
|
3264
|
-
assert(!markdown.includes('$203,746.00'), 'Expected invoice number to remain numeric');
|
|
3265
|
-
assert(!markdown.includes('$558.00'), 'Expected duplicate count total to remain numeric');
|
|
3266
|
-
assert(markdown.includes('203,746'), 'Expected large invoice number to be number-formatted');
|
|
3267
|
-
}
|
|
3268
|
-
|
|
3269
|
-
function testDisplayMarkdownCurrencyColumnsStillUseCurrencyFormat() {
|
|
3270
|
-
const display = buildDisplayTable([
|
|
3271
|
-
{ invoice_number: 1001, grand_total: 1234.5, sales_tax: 88.1, paid_total: 500 }
|
|
3272
|
-
]);
|
|
3273
|
-
const markdown = formatDisplayTableMarkdown(display);
|
|
3274
|
-
assert(markdown.includes('$1,234.50'), 'Expected grand_total to be currency formatted');
|
|
3275
|
-
assert(markdown.includes('$88.10'), 'Expected sales_tax to be currency formatted');
|
|
3276
|
-
assert(markdown.includes('$500.00'), 'Expected paid_total to be currency formatted');
|
|
3277
|
-
assert(!markdown.includes('$1,001.00'), 'Expected invoice_number to not be currency formatted');
|
|
3278
|
-
}
|
|
3279
|
-
|
|
3280
|
-
function testDisplayMarkdownKeepsAlphanumericBlendAndChemicalNames() {
|
|
3281
|
-
const display = buildDisplayTable([
|
|
3282
|
-
{
|
|
3283
|
-
blend_name: 'ASFlow-100 Bulk Winter Blend (Surf Pkg)',
|
|
3284
|
-
chemical: 'INT 50/50 - CO INDEPENDENCE',
|
|
3285
|
-
chemical_recipe_quantity: 4500
|
|
3286
|
-
}
|
|
3287
|
-
]);
|
|
3288
|
-
const markdown = formatDisplayTableMarkdown(display);
|
|
3289
|
-
assert(markdown.includes('ASFlow-100 Bulk Winter Blend (Surf Pkg)'), 'Expected blend name to remain text');
|
|
3290
|
-
assert(markdown.includes('INT 50/50 - CO INDEPENDENCE'), 'Expected chemical name to remain text');
|
|
3291
|
-
assert(!markdown.includes('| -100 |'), 'Expected blend/chemical names to not be coerced into numeric -100');
|
|
3292
|
-
assert(!markdown.includes('| 5,050 |'), 'Expected blend/chemical names to not be coerced into numeric 5,050');
|
|
3293
|
-
}
|
|
3294
|
-
|
|
3295
|
-
function testDisplayTableCoalescesStringVariantFields() {
|
|
3296
|
-
const display = buildDisplayTable([
|
|
3297
|
-
{
|
|
3298
|
-
batch_number: 202792,
|
|
3299
|
-
batch_number_string: '202,792',
|
|
3300
|
-
createdAt: '2026-02-06T21:47:00.000Z'
|
|
3301
|
-
},
|
|
3302
|
-
{
|
|
3303
|
-
batch_number: null,
|
|
3304
|
-
batch_number_string: '202,791',
|
|
3305
|
-
createdAt: '2026-02-06T20:18:00.000Z'
|
|
3306
|
-
}
|
|
3307
|
-
]);
|
|
3308
|
-
assert(display.columns.includes('Batch Number'), 'Expected base batch number column');
|
|
3309
|
-
assert(!display.columns.includes('Batch Number String'), 'Expected string variant batch column to be removed');
|
|
3310
|
-
const secondRow = display.rows?.[1] || {};
|
|
3311
|
-
assert(String(secondRow['Batch Number'] || '').includes('202,791'), 'Expected fallback from batch_number_string into Batch Number');
|
|
3312
|
-
}
|
|
3313
|
-
|
|
3314
|
-
function testDisplayTableCoalescesEquivalentCreatedDateFields() {
|
|
3315
|
-
const display = buildDisplayTable([
|
|
3316
|
-
{
|
|
3317
|
-
createdAt: '2026-02-06T21:47:00.000Z',
|
|
3318
|
-
date: '2026-02-06T21:47:00.000Z',
|
|
3319
|
-
blend_name: 'Blend A'
|
|
3320
|
-
},
|
|
3321
|
-
{
|
|
3322
|
-
createdAt: '2026-02-06T20:18:00.000Z',
|
|
3323
|
-
date: '2026-02-06T20:18:00.000Z',
|
|
3324
|
-
blend_name: 'Blend B'
|
|
3325
|
-
}
|
|
3326
|
-
]);
|
|
3327
|
-
assert(display.columns.includes('Created At'), 'Expected Created At column to remain');
|
|
3328
|
-
assert(!display.columns.includes('Date'), 'Expected duplicate Date column to be removed');
|
|
3329
|
-
}
|
|
3330
|
-
|
|
3331
|
-
function testDisplayTableKeepsDistinctCreatedDateAndDateFields() {
|
|
3332
|
-
const display = buildDisplayTable([
|
|
3333
|
-
{
|
|
3334
|
-
createdAt: '2026-02-06T21:47:00.000Z',
|
|
3335
|
-
date: '2026-02-01T00:00:00.000Z',
|
|
3336
|
-
blend_name: 'Blend A'
|
|
3337
|
-
},
|
|
3338
|
-
{
|
|
3339
|
-
createdAt: '2026-02-06T20:18:00.000Z',
|
|
3340
|
-
date: '2026-02-01T00:00:00.000Z',
|
|
3341
|
-
blend_name: 'Blend B'
|
|
3342
|
-
}
|
|
3343
|
-
]);
|
|
3344
|
-
assert(display.columns.includes('Created At'), 'Expected Created At column to remain');
|
|
3345
|
-
assert(display.columns.includes('Date'), 'Expected Date column to remain when values differ');
|
|
3346
|
-
}
|
|
3347
|
-
|
|
3348
|
-
function testStripQueryFieldPathsDeep() {
|
|
3349
|
-
const query = { $and: [{ id_customer: 'Acme' }, { status: 'open' }] };
|
|
3350
|
-
const stripped = stripQueryFieldPathsDeep(query, ['id_customer', 'other.id_customer']);
|
|
3351
|
-
assert(JSON.stringify(stripped) === JSON.stringify({ status: 'open' }), 'Expected id_customer to be stripped');
|
|
3352
|
-
|
|
3353
|
-
const nested = { other: { id_customer: 'Acme', foo: 1 }, status: 'open' };
|
|
3354
|
-
const strippedNested = stripQueryFieldPathsDeep(nested, ['id_customer', 'other.id_customer']);
|
|
3355
|
-
assert(
|
|
3356
|
-
JSON.stringify(strippedNested) === JSON.stringify({ other: { foo: 1 }, status: 'open' }),
|
|
3357
|
-
'Expected nested other.id_customer to be stripped'
|
|
3358
|
-
);
|
|
3359
|
-
|
|
3360
|
-
const pipeline = [{ $match: { id_customer: 'Acme', status: 'open' } }];
|
|
3361
|
-
const strippedPipeline = stripScopedFieldsFromPipeline(pipeline, ['id_customer', 'other.id_customer']);
|
|
3362
|
-
assert(
|
|
3363
|
-
JSON.stringify(strippedPipeline[0]?.$match) === JSON.stringify({ status: 'open' }),
|
|
3364
|
-
'Expected pipeline $match id_customer to be stripped'
|
|
3365
|
-
);
|
|
3366
|
-
}
|
|
3367
|
-
|
|
3368
|
-
function testResolveReadMultiTermJobRegexFallbackForTesting() {
|
|
3369
|
-
const query = {
|
|
3370
|
-
$and: [
|
|
3371
|
-
{
|
|
3372
|
-
$or: [
|
|
3373
|
-
{ job_number_string: { $regex: 'hazelwood', $options: 'i' } },
|
|
3374
|
-
{ job_number: { $regex: 'hazelwood', $options: 'i' } },
|
|
3375
|
-
{ bid: { $regex: 'hazelwood', $options: 'i' } },
|
|
3376
|
-
{ jca: { $regex: 'hazelwood', $options: 'i' } }
|
|
3377
|
-
]
|
|
3378
|
-
},
|
|
3379
|
-
{
|
|
3380
|
-
$or: [
|
|
3381
|
-
{ job_number_string: { $regex: 'cross', $options: 'i' } },
|
|
3382
|
-
{ job_number: { $regex: 'cross', $options: 'i' } },
|
|
3383
|
-
{ bid: { $regex: 'cross', $options: 'i' } },
|
|
3384
|
-
{ jca: { $regex: 'cross', $options: 'i' } }
|
|
3385
|
-
]
|
|
3386
|
-
},
|
|
3387
|
-
{
|
|
3388
|
-
$or: [
|
|
3389
|
-
{ customer: { $regex: 'pioneer', $options: 'i' } },
|
|
3390
|
-
{ operator: { $regex: 'pioneer', $options: 'i' } }
|
|
3391
|
-
]
|
|
3392
|
-
}
|
|
3393
|
-
]
|
|
3394
|
-
};
|
|
3395
|
-
const probeDocs = [
|
|
3396
|
-
{
|
|
3397
|
-
job_number_string: 'J-1001',
|
|
3398
|
-
pad: 'Hazelwood Cross',
|
|
3399
|
-
wells: [{ well: 'Hazelwood Cross #1' }],
|
|
3400
|
-
customer: 'Pioneer',
|
|
3401
|
-
operator: 'Pioneer'
|
|
3402
|
-
}
|
|
3403
|
-
];
|
|
3404
|
-
const schemaFields = ['job_number_string', 'job_number', 'bid', 'jca', 'pad', 'wells.$.well', 'customer', 'operator'];
|
|
3405
|
-
const fallback = resolveReadMultiTermJobRegexFallbackForTesting(query as any, probeDocs, schemaFields);
|
|
3406
|
-
assert(!!fallback, 'Expected multi-term job regex fallback to resolve');
|
|
3407
|
-
assert(
|
|
3408
|
-
fallback?.terms.includes('hazelwood') && fallback?.terms.includes('cross'),
|
|
3409
|
-
'Expected fallback terms to include hazelwood and cross'
|
|
3410
|
-
);
|
|
3411
|
-
assert(
|
|
3412
|
-
fallback?.fields.includes('pad') || fallback?.fields.includes('wells.well'),
|
|
3413
|
-
'Expected fallback fields to include pad or wells.well'
|
|
3414
|
-
);
|
|
3415
|
-
const serialized = JSON.stringify(fallback?.query || {});
|
|
3416
|
-
assert(serialized.includes('pioneer'), 'Expected non-job customer/operator filters to be preserved');
|
|
3417
|
-
assert(serialized.includes('hazelwood') && serialized.includes('cross'), 'Expected fallback query to preserve both job terms');
|
|
3418
|
-
}
|
|
3419
|
-
|
|
3420
|
-
function testNormalizeIdsForTargetField() {
|
|
3421
|
-
const hex = '64b7f3f5c3a18e2f2b4a1f01';
|
|
3422
|
-
const objectId = new ObjectId(hex);
|
|
3423
|
-
const stringDocs = [{ id_customer: 'CUST-123' }];
|
|
3424
|
-
const normalizedStrings = normalizeIdsForTargetField([objectId], stringDocs, 'id_customer');
|
|
3425
|
-
assert(
|
|
3426
|
-
Array.isArray(normalizedStrings) && normalizedStrings[0] === hex,
|
|
3427
|
-
'Expected ObjectId to normalize to string hex for string fields'
|
|
3428
|
-
);
|
|
3429
|
-
|
|
3430
|
-
const objectDocs = [{ id_customer: objectId }];
|
|
3431
|
-
const normalizedObjects = normalizeIdsForTargetField([hex], objectDocs, 'id_customer');
|
|
3432
|
-
const normalizedIsObjectId = normalizedObjects.some(value => value && typeof value.toHexString === 'function');
|
|
3433
|
-
assert(normalizedIsObjectId, 'Expected string hex to normalize to ObjectId for objectId fields');
|
|
3434
|
-
}
|
|
3435
|
-
|
|
3436
|
-
function testRewriteEmbeddedMatchObjects() {
|
|
3437
|
-
const query = { customer: { name: 'Acme' }, status: 'open' };
|
|
3438
|
-
const rewritten = rewriteEmbeddedMatchObjects(query);
|
|
3439
|
-
assert(
|
|
3440
|
-
JSON.stringify(rewritten) === JSON.stringify({ 'customer.name': 'Acme', status: 'open' }),
|
|
3441
|
-
'Expected embedded match object to be rewritten to dot notation'
|
|
3442
|
-
);
|
|
3443
|
-
|
|
3444
|
-
const nestedOperator = { customer: { name: { $regex: 'Acme', $options: 'i' } } };
|
|
3445
|
-
const rewrittenOperator = rewriteEmbeddedMatchObjects(nestedOperator);
|
|
3446
|
-
assert(
|
|
3447
|
-
!!rewrittenOperator['customer.name']?.$regex,
|
|
3448
|
-
'Expected operator object to be preserved under dot notation'
|
|
3449
|
-
);
|
|
3450
|
-
|
|
3451
|
-
const fieldOperator = { customer: { $in: ['A', 'B'] } };
|
|
3452
|
-
const rewrittenFieldOperator = rewriteEmbeddedMatchObjects(fieldOperator);
|
|
3453
|
-
assert(
|
|
3454
|
-
JSON.stringify(rewrittenFieldOperator) === JSON.stringify(fieldOperator),
|
|
3455
|
-
'Expected field-level operator objects to remain unchanged'
|
|
3456
|
-
);
|
|
3457
|
-
|
|
3458
|
-
const logicalOperator = {
|
|
3459
|
-
$or: [
|
|
3460
|
-
{ customer: { name: 'Acme' } },
|
|
3461
|
-
{ customer: { name: 'Globex' } }
|
|
3462
|
-
]
|
|
3463
|
-
};
|
|
3464
|
-
const rewrittenLogical = rewriteEmbeddedMatchObjects(logicalOperator);
|
|
3465
|
-
assert(
|
|
3466
|
-
JSON.stringify(rewrittenLogical) === JSON.stringify({
|
|
3467
|
-
$or: [
|
|
3468
|
-
{ 'customer.name': 'Acme' },
|
|
3469
|
-
{ 'customer.name': 'Globex' }
|
|
3470
|
-
]
|
|
3471
|
-
}),
|
|
3472
|
-
'Expected logical operator entries to be flattened'
|
|
3473
|
-
);
|
|
3474
|
-
}
|
|
3475
|
-
|
|
3476
|
-
function testRewriteMatchExpressionsToExpr() {
|
|
3477
|
-
const query = {
|
|
3478
|
-
status: 'Paid/Closed',
|
|
3479
|
-
date_paid: {
|
|
3480
|
-
$gte: { $dateSubtract: { startDate: '$$NOW', unit: 'month', amount: 6 } },
|
|
3481
|
-
$lte: '$$NOW'
|
|
3482
|
-
}
|
|
3483
|
-
};
|
|
3484
|
-
const rewritten = rewriteMatchExpressionsToExpr(query);
|
|
3485
|
-
assert(
|
|
3486
|
-
JSON.stringify(rewritten) === JSON.stringify({
|
|
3487
|
-
status: 'Paid/Closed',
|
|
3488
|
-
$expr: {
|
|
3489
|
-
$and: [
|
|
3490
|
-
{ $gte: ['$date_paid', { $dateSubtract: { startDate: '$$NOW', unit: 'month', amount: 6 } }] },
|
|
3491
|
-
{ $lte: ['$date_paid', '$$NOW'] }
|
|
3492
|
-
]
|
|
3493
|
-
}
|
|
3494
|
-
}),
|
|
3495
|
-
'Expected expression-style match operators to be rewritten into $expr'
|
|
3496
|
-
);
|
|
3497
|
-
}
|
|
3498
|
-
|
|
3499
|
-
function testRewriteMatchExpressionsToExprCompactNowToken() {
|
|
3500
|
-
const query = {
|
|
3501
|
-
date_paid: {
|
|
3502
|
-
$gte: '$$NOW-6M',
|
|
3503
|
-
$lte: '$$NOW'
|
|
3504
|
-
}
|
|
3505
|
-
};
|
|
3506
|
-
const rewritten = rewriteMatchExpressionsToExpr(query);
|
|
3507
|
-
assert(
|
|
3508
|
-
JSON.stringify(rewritten) === JSON.stringify({
|
|
3509
|
-
$expr: {
|
|
3510
|
-
$and: [
|
|
3511
|
-
{ $gte: ['$date_paid', { $dateSubtract: { startDate: '$$NOW', unit: 'month', amount: 6 } }] },
|
|
3512
|
-
{ $lte: ['$date_paid', '$$NOW'] }
|
|
3513
|
-
]
|
|
3514
|
-
}
|
|
3515
|
-
}),
|
|
3516
|
-
'Expected compact $$NOW-6M token to be normalized into $dateSubtract'
|
|
3517
|
-
);
|
|
3518
|
-
}
|
|
3519
|
-
|
|
3520
|
-
function testNormalizeAssistantNowExprPlaceholdersDateArithmeticArgs() {
|
|
3521
|
-
const query = {
|
|
3522
|
-
$expr: {
|
|
3523
|
-
$gte: [
|
|
3524
|
-
'$date_paid',
|
|
3525
|
-
{ $dateSubtract: { date: '$$NOW', unit: 'month', amount: 6 } }
|
|
3526
|
-
]
|
|
3527
|
-
}
|
|
3528
|
-
};
|
|
3529
|
-
const normalized = normalizeAssistantNowExprPlaceholders(query);
|
|
3530
|
-
const dateSubtractArgs = normalized?.$expr?.$gte?.[1]?.$dateSubtract;
|
|
3531
|
-
assert(!!dateSubtractArgs, 'Expected $dateSubtract args to exist');
|
|
3532
|
-
assert(dateSubtractArgs.startDate === '$$NOW', 'Expected $dateSubtract.date to normalize to startDate');
|
|
3533
|
-
assert(!Object.prototype.hasOwnProperty.call(dateSubtractArgs, 'date'), 'Expected legacy $dateSubtract.date arg to be removed');
|
|
3534
|
-
}
|
|
3535
|
-
|
|
3536
|
-
function testNormalizeAssistantNowExprPlaceholdersDateArithmeticUnknownKeyAsStartDate() {
|
|
3537
|
-
const query = {
|
|
3538
|
-
$expr: {
|
|
3539
|
-
$gte: [
|
|
3540
|
-
'$date_paid',
|
|
3541
|
-
{ $dateSubtract: { 'misc_charges.amount': '$$NOW', unit: 'month', amount: 6 } }
|
|
3542
|
-
]
|
|
3543
|
-
}
|
|
3544
|
-
};
|
|
3545
|
-
const normalized = normalizeAssistantNowExprPlaceholders(query);
|
|
3546
|
-
const dateSubtractArgs = normalized?.$expr?.$gte?.[1]?.$dateSubtract;
|
|
3547
|
-
assert(!!dateSubtractArgs, 'Expected $dateSubtract args to exist');
|
|
3548
|
-
assert(dateSubtractArgs.startDate === '$$NOW', 'Expected unknown key value to normalize to startDate');
|
|
3549
|
-
assert(dateSubtractArgs.amount === 6, 'Expected amount to remain numeric');
|
|
3550
|
-
assert(!Object.prototype.hasOwnProperty.call(dateSubtractArgs, 'misc_charges.amount'), 'Expected unknown key to be removed');
|
|
3551
|
-
}
|
|
3552
|
-
|
|
3553
|
-
function testNormalizeAssistantNowExprPlaceholdersDateArithmeticUnknownKeyAsAmount() {
|
|
3554
|
-
const query = {
|
|
3555
|
-
$expr: {
|
|
3556
|
-
$gte: [
|
|
3557
|
-
'$date_paid',
|
|
3558
|
-
{ $dateSubtract: { startDate: '$$NOW', unit: 'month', 'misc_charges.amount': 6 } }
|
|
3559
|
-
]
|
|
3560
|
-
}
|
|
3561
|
-
};
|
|
3562
|
-
const normalized = normalizeAssistantNowExprPlaceholders(query);
|
|
3563
|
-
const dateSubtractArgs = normalized?.$expr?.$gte?.[1]?.$dateSubtract;
|
|
3564
|
-
assert(!!dateSubtractArgs, 'Expected $dateSubtract args to exist');
|
|
3565
|
-
assert(dateSubtractArgs.startDate === '$$NOW', 'Expected startDate to remain intact');
|
|
3566
|
-
assert(dateSubtractArgs.amount === 6, 'Expected unknown numeric key to normalize to amount');
|
|
3567
|
-
assert(!Object.prototype.hasOwnProperty.call(dateSubtractArgs, 'misc_charges.amount'), 'Expected unknown key to be removed');
|
|
3568
|
-
}
|
|
3569
|
-
|
|
3570
|
-
function testNormalizeAssistantMonthlyCalendarWindowPipelineConvertsRollingMonthlyRange() {
|
|
3571
|
-
const pipeline = [
|
|
3572
|
-
{
|
|
3573
|
-
$match: {
|
|
3574
|
-
$expr: {
|
|
3575
|
-
$and: [
|
|
3576
|
-
{
|
|
3577
|
-
$gte: [
|
|
3578
|
-
'$date_invoice',
|
|
3579
|
-
{ $dateSubtract: { startDate: '$$NOW', unit: 'month', amount: 6 } }
|
|
3580
|
-
]
|
|
3581
|
-
},
|
|
3582
|
-
{ $lte: ['$date_invoice', '$$NOW'] }
|
|
3583
|
-
]
|
|
3584
|
-
}
|
|
3585
|
-
}
|
|
3586
|
-
},
|
|
3587
|
-
{
|
|
3588
|
-
$group: {
|
|
3589
|
-
_id: {
|
|
3590
|
-
$dateToString: {
|
|
3591
|
-
format: '%Y-%m',
|
|
3592
|
-
date: '$date_invoice'
|
|
3593
|
-
}
|
|
3594
|
-
},
|
|
3595
|
-
total_revenue: { $sum: '$grand_total' }
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
];
|
|
3599
|
-
const normalized = normalizeAssistantMonthlyCalendarWindowPipeline(pipeline as any);
|
|
3600
|
-
const andClauses = normalized?.[0]?.$match?.$expr?.$and;
|
|
3601
|
-
assert(Array.isArray(andClauses), 'Expected normalized $expr.$and clauses');
|
|
3602
|
-
const lowerClause = andClauses.find((entry: any) => entry?.$gte?.[0] === '$date_invoice');
|
|
3603
|
-
assert(!!lowerClause, 'Expected lower month bound clause');
|
|
3604
|
-
const lowerSubtract = lowerClause?.$gte?.[1]?.$dateSubtract;
|
|
3605
|
-
assert(!!lowerSubtract, 'Expected $dateSubtract lower bound');
|
|
3606
|
-
assert(lowerSubtract?.startDate?.$dateTrunc?.date === '$$NOW', 'Expected lower bound anchored to month start');
|
|
3607
|
-
assert(lowerSubtract?.startDate?.$dateTrunc?.unit === 'month', 'Expected lower bound month truncation');
|
|
3608
|
-
assert(lowerSubtract?.amount === 6, 'Expected month count preserved');
|
|
3609
|
-
const upperClause = andClauses.find((entry: any) => entry?.$lt?.[0] === '$date_invoice');
|
|
3610
|
-
assert(!!upperClause, 'Expected strict upper month-start clause');
|
|
3611
|
-
assert(upperClause?.$lt?.[1]?.$dateTrunc?.date === '$$NOW', 'Expected upper bound anchored to current month start');
|
|
3612
|
-
assert(upperClause?.$lt?.[1]?.$dateTrunc?.unit === 'month', 'Expected upper bound month truncation');
|
|
3613
|
-
assert(!andClauses.some((entry: any) => entry?.$lte?.[1] === '$$NOW'), 'Expected rolling $$NOW upper bound removed');
|
|
3614
|
-
}
|
|
3615
|
-
|
|
3616
|
-
function testNormalizeAssistantMonthlyCalendarWindowPipelineSkipsDailyGrouping() {
|
|
3617
|
-
const pipeline = [
|
|
3618
|
-
{
|
|
3619
|
-
$match: {
|
|
3620
|
-
$expr: {
|
|
3621
|
-
$and: [
|
|
3622
|
-
{
|
|
3623
|
-
$gte: [
|
|
3624
|
-
'$date_invoice',
|
|
3625
|
-
{ $dateSubtract: { startDate: '$$NOW', unit: 'month', amount: 6 } }
|
|
3626
|
-
]
|
|
3627
|
-
},
|
|
3628
|
-
{ $lte: ['$date_invoice', '$$NOW'] }
|
|
3629
|
-
]
|
|
3630
|
-
}
|
|
3631
|
-
}
|
|
3632
|
-
},
|
|
3633
|
-
{
|
|
3634
|
-
$group: {
|
|
3635
|
-
_id: {
|
|
3636
|
-
$dateToString: {
|
|
3637
|
-
format: '%Y-%m-%d',
|
|
3638
|
-
date: '$date_invoice'
|
|
3639
|
-
}
|
|
3640
|
-
},
|
|
3641
|
-
total_revenue: { $sum: '$grand_total' }
|
|
3642
|
-
}
|
|
3643
|
-
}
|
|
3644
|
-
];
|
|
3645
|
-
const normalized = normalizeAssistantMonthlyCalendarWindowPipeline(pipeline as any);
|
|
3646
|
-
const andClauses = normalized?.[0]?.$match?.$expr?.$and;
|
|
3647
|
-
assert(Array.isArray(andClauses), 'Expected daily grouping to preserve $and clauses');
|
|
3648
|
-
assert(andClauses.some((entry: any) => entry?.$lte?.[1] === '$$NOW'), 'Expected rolling $$NOW upper bound to remain for non-monthly grouping');
|
|
3649
|
-
}
|
|
3650
|
-
|
|
3651
|
-
function testExpandAggregateDateMatchFallbackExpandsNestedOrClauses() {
|
|
3652
|
-
const pipeline = [
|
|
3653
|
-
{
|
|
3654
|
-
$match: {
|
|
3655
|
-
$and: [
|
|
3656
|
-
{
|
|
3657
|
-
$or: [
|
|
3658
|
-
{
|
|
3659
|
-
date_created: { $gte: new Date('2026-02-16T00:00:00.000Z') },
|
|
3660
|
-
$expr: { $lte: ['$date_created', '$$NOW'] }
|
|
3661
|
-
},
|
|
3662
|
-
{
|
|
3663
|
-
createdAt: { $gte: new Date('2026-02-16T00:00:00.000Z') },
|
|
3664
|
-
$expr: { $lte: ['$createdAt', '$$NOW'] }
|
|
3665
|
-
}
|
|
3666
|
-
]
|
|
3667
|
-
}
|
|
3668
|
-
]
|
|
3669
|
-
}
|
|
3670
|
-
},
|
|
3671
|
-
{
|
|
3672
|
-
$group: {
|
|
3673
|
-
_id: '$customer',
|
|
3674
|
-
work_orders: { $sum: 1 }
|
|
3675
|
-
}
|
|
3676
|
-
}
|
|
3677
|
-
];
|
|
3678
|
-
const expanded = expandAggregateDateMatchFallbackForTesting(
|
|
3679
|
-
pipeline as any,
|
|
3680
|
-
['date_completed', 'date_closed', 'date_created', 'createdAt']
|
|
3681
|
-
);
|
|
3682
|
-
assert(!!expanded, 'Expected aggregate date fallback to expand nested $or clauses');
|
|
3683
|
-
assert(
|
|
3684
|
-
Array.isArray(expanded?.fields) && expanded?.fields.includes('date_completed') && expanded?.fields.includes('date_closed'),
|
|
3685
|
-
'Expected aggregate date fallback to add completed and closed date fields'
|
|
3686
|
-
);
|
|
3687
|
-
const expandedOr = expanded?.pipeline?.[0]?.$match?.$and?.[0]?.$or;
|
|
3688
|
-
assert(Array.isArray(expandedOr), 'Expected expanded nested $or array');
|
|
3689
|
-
assert(
|
|
3690
|
-
expandedOr.some((entry: any) => Object.prototype.hasOwnProperty.call(entry, 'date_completed')),
|
|
3691
|
-
'Expected nested $or to include date_completed fallback branch'
|
|
3692
|
-
);
|
|
3693
|
-
assert(
|
|
3694
|
-
expandedOr.some((entry: any) => Object.prototype.hasOwnProperty.call(entry, 'date_closed')),
|
|
3695
|
-
'Expected nested $or to include date_closed fallback branch'
|
|
3696
|
-
);
|
|
3697
|
-
}
|
|
3698
|
-
|
|
3699
|
-
function testNormalizeAssistantAggregatePipelineRankSortBySingleKey() {
|
|
3700
|
-
const pipeline = [
|
|
3701
|
-
{
|
|
3702
|
-
$setWindowFields: {
|
|
3703
|
-
partitionBy: null,
|
|
3704
|
-
sortBy: {
|
|
3705
|
-
total_by_driver: -1,
|
|
3706
|
-
driver: 1
|
|
3707
|
-
},
|
|
3708
|
-
output: {
|
|
3709
|
-
driver_rank: { $rank: {} }
|
|
3710
|
-
}
|
|
3711
|
-
}
|
|
3712
|
-
}
|
|
3713
|
-
];
|
|
3714
|
-
const normalized = normalizeAssistantAggregatePipeline(pipeline as any, 'production-deliveries');
|
|
3715
|
-
const rankStage = normalized?.[0]?.$setWindowFields;
|
|
3716
|
-
const sortBy = rankStage?.sortBy || {};
|
|
3717
|
-
const keys = Object.keys(sortBy);
|
|
3718
|
-
assert(keys.length === 1, 'Expected rank sortBy to be normalized to exactly one key');
|
|
3719
|
-
assert(keys[0] === 'total_by_driver', 'Expected first sortBy key to be preserved for rank');
|
|
3720
|
-
assert(sortBy.total_by_driver === -1, 'Expected rank sort direction to be preserved');
|
|
3721
|
-
}
|
|
3722
|
-
|
|
3723
|
-
function testNormalizeAssistantAggregatePipelineMovesLastNLimitBeforeGroup() {
|
|
3724
|
-
const pipeline = [
|
|
3725
|
-
{
|
|
3726
|
-
$match: {
|
|
3727
|
-
date_created: {
|
|
3728
|
-
$gte: new Date('2026-02-09T00:00:00.000Z'),
|
|
3729
|
-
$lt: new Date('2026-02-10T00:00:00.000Z')
|
|
3730
|
-
}
|
|
3731
|
-
}
|
|
3732
|
-
},
|
|
3733
|
-
{
|
|
3734
|
-
$group: {
|
|
3735
|
-
_id: '$status',
|
|
3736
|
-
count: { $sum: 1 }
|
|
3737
|
-
}
|
|
3738
|
-
},
|
|
3739
|
-
{ $sort: { count: -1 } },
|
|
3740
|
-
{ $limit: 20 }
|
|
3741
|
-
];
|
|
3742
|
-
const normalized = normalizeAssistantAggregatePipeline(
|
|
3743
|
-
pipeline as any,
|
|
3744
|
-
'maintenance-orders',
|
|
3745
|
-
'Show me the last 20 work orders created this week, grouped by status.'
|
|
3746
|
-
);
|
|
3747
|
-
const groupIndex = normalized.findIndex((stage: any) => !!stage?.$group);
|
|
3748
|
-
const limitIndex = normalized.findIndex((stage: any) => typeof stage?.$limit === 'number');
|
|
3749
|
-
const sortIndex = normalized.findIndex((stage: any) => !!stage?.$sort && stage.$sort.date_created === -1);
|
|
3750
|
-
assert(groupIndex > -1, 'Expected group stage to remain in pipeline');
|
|
3751
|
-
assert(limitIndex > -1, 'Expected limit stage to exist');
|
|
3752
|
-
assert(sortIndex > -1, 'Expected created-date sort stage to be added before grouping');
|
|
3753
|
-
assert(limitIndex < groupIndex, 'Expected record limit to be applied before grouping');
|
|
3754
|
-
assert(sortIndex < limitIndex, 'Expected record sort to happen before record limit');
|
|
3755
|
-
assert((normalized[limitIndex] as any).$limit === 20, 'Expected requested record limit to be preserved');
|
|
3756
|
-
}
|
|
3757
|
-
|
|
3758
|
-
function testNormalizeAssistantAggregatePipelineKeepsTrendGroupingLimitAfterGroup() {
|
|
3759
|
-
const pipeline = [
|
|
3760
|
-
{
|
|
3761
|
-
$match: {
|
|
3762
|
-
date_invoice: {
|
|
3763
|
-
$gte: new Date('2025-08-01T00:00:00.000Z'),
|
|
3764
|
-
$lt: new Date('2026-02-01T00:00:00.000Z')
|
|
3765
|
-
}
|
|
3766
|
-
}
|
|
3767
|
-
},
|
|
3768
|
-
{
|
|
3769
|
-
$group: {
|
|
3770
|
-
_id: {
|
|
3771
|
-
$dateToString: {
|
|
3772
|
-
format: '%Y-%m',
|
|
3773
|
-
date: '$date_invoice'
|
|
3774
|
-
}
|
|
3775
|
-
},
|
|
3776
|
-
total: { $sum: '$grand_total' }
|
|
3777
|
-
}
|
|
3778
|
-
},
|
|
3779
|
-
{ $sort: { _id: 1 } },
|
|
3780
|
-
{ $limit: 20 }
|
|
3781
|
-
];
|
|
3782
|
-
const normalized = normalizeAssistantAggregatePipeline(
|
|
3783
|
-
pipeline as any,
|
|
3784
|
-
'invoices',
|
|
3785
|
-
'Break down total revenue over the last 6 months by month.'
|
|
3786
|
-
);
|
|
3787
|
-
const groupIndex = normalized.findIndex((stage: any) => !!stage?.$group);
|
|
3788
|
-
const limitIndex = normalized.findIndex((stage: any) => typeof stage?.$limit === 'number');
|
|
3789
|
-
assert(groupIndex > -1, 'Expected group stage to remain in trend pipeline');
|
|
3790
|
-
assert(limitIndex > groupIndex, 'Expected trend/month grouping limit to remain post-group');
|
|
3791
|
-
}
|
|
3792
|
-
|
|
3793
|
-
function testBuildAssistantToolRequestRestoresMongoSafeDirectiveKeys() {
|
|
3794
|
-
const request = buildAssistantToolRequest({
|
|
3795
|
-
type: 'aggregate',
|
|
3796
|
-
payload: {
|
|
3797
|
-
collection: 'customers',
|
|
3798
|
-
pipeline: [
|
|
3799
|
-
{
|
|
3800
|
-
_dollar_match: {
|
|
3801
|
-
'customer__dot__name': 'Acme',
|
|
3802
|
-
total: { _dollar_gt: 0 }
|
|
3803
|
-
}
|
|
3804
|
-
}
|
|
3805
|
-
]
|
|
3806
|
-
},
|
|
3807
|
-
cleaned: '',
|
|
3808
|
-
rawLine: 'REPORT_BUILDER_AGG: {}'
|
|
3809
|
-
} as any, {
|
|
3810
|
-
message: 'Show active clients'
|
|
3811
|
-
} as any);
|
|
3812
|
-
assert(request?.pipeline?.[0]?.$match, 'Expected Mongo-safe stage keys to restore to $ operators');
|
|
3813
|
-
assert(
|
|
3814
|
-
request?.pipeline?.[0]?.$match?.['customer.name'] === 'Acme',
|
|
3815
|
-
'Expected Mongo-safe dotted field names to restore before tool execution'
|
|
3816
|
-
);
|
|
3817
|
-
assert(
|
|
3818
|
-
request?.pipeline?.[0]?.$match?.total?.$gt === 0,
|
|
3819
|
-
'Expected nested Mongo-safe operators to restore before tool execution'
|
|
3820
|
-
);
|
|
3821
|
-
}
|
|
3822
|
-
|
|
3823
|
-
function testRepairAssistantFieldPathReferenceInPipelineUsesGetFieldForDottedPath() {
|
|
3824
|
-
const pipeline = [
|
|
3825
|
-
{
|
|
3826
|
-
$addFields: {
|
|
3827
|
-
volume_unit: '$blend_chemicals.unit',
|
|
3828
|
-
volume_value: '$blend_chemicals.containers.quantity'
|
|
3829
|
-
}
|
|
3830
|
-
}
|
|
3831
|
-
];
|
|
3832
|
-
const repaired = repairAssistantFieldPathReferenceInPipeline(pipeline as any, 'blend_chemicals.unit');
|
|
3833
|
-
assert(repaired.changed, 'Expected dotted field-path references to be rewritten');
|
|
3834
|
-
const unitExpr = repaired.pipeline?.[0]?.$addFields?.volume_unit;
|
|
3835
|
-
assert(!!unitExpr?.$getField, 'Expected volume_unit to use $getField expression');
|
|
3836
|
-
assert(unitExpr?.$getField?.field === 'unit', 'Expected final $getField segment to target unit');
|
|
3837
|
-
assert(unitExpr?.$getField?.input === '$blend_chemicals', 'Expected $getField input to be blend_chemicals root');
|
|
3838
|
-
const quantityExpr = repaired.pipeline?.[0]?.$addFields?.volume_value;
|
|
3839
|
-
assert(
|
|
3840
|
-
quantityExpr === '$blend_chemicals.containers.quantity',
|
|
3841
|
-
'Expected non-targeted field path references to remain unchanged'
|
|
3842
|
-
);
|
|
3843
|
-
}
|
|
3844
|
-
|
|
3845
|
-
function testRepairAssistantPositionalPathSegmentsInPipeline() {
|
|
3846
|
-
const pipeline = [
|
|
3847
|
-
{
|
|
3848
|
-
$match: {
|
|
3849
|
-
'routes.$.date_needed.date': {
|
|
3850
|
-
$gte: new Date('2026-02-16T00:00:00.000Z')
|
|
3851
|
-
}
|
|
3852
|
-
}
|
|
3853
|
-
},
|
|
3854
|
-
{
|
|
3855
|
-
$group: {
|
|
3856
|
-
_id: '$routes.$.id_location',
|
|
3857
|
-
count: { $sum: 1 }
|
|
3858
|
-
}
|
|
3859
|
-
},
|
|
3860
|
-
{
|
|
3861
|
-
$sort: {
|
|
3862
|
-
'_id.routes.$.id_location': 1
|
|
3863
|
-
}
|
|
3864
|
-
},
|
|
3865
|
-
{
|
|
3866
|
-
$project: {
|
|
3867
|
-
route_date: '$routes.$.date_needed.date'
|
|
3868
|
-
}
|
|
3869
|
-
}
|
|
3870
|
-
];
|
|
3871
|
-
const repaired = repairAssistantPositionalPathSegmentsInPipeline(pipeline as any);
|
|
3872
|
-
assert(repaired.changed, 'Expected positional path segments to be repaired');
|
|
3873
|
-
const matchKeys = Object.keys(repaired.pipeline?.[0]?.$match || {});
|
|
3874
|
-
assert(
|
|
3875
|
-
matchKeys.includes('routes.date_needed.date'),
|
|
3876
|
-
'Expected positional $ segment to be removed from match field'
|
|
3877
|
-
);
|
|
3878
|
-
assert(
|
|
3879
|
-
!matchKeys.includes('routes.$.date_needed.date'),
|
|
3880
|
-
'Expected legacy positional match field to be removed'
|
|
3881
|
-
);
|
|
3882
|
-
assert(
|
|
3883
|
-
repaired.pipeline?.[1]?.$group?._id === '$routes.id_location',
|
|
3884
|
-
'Expected positional $ segment to be removed from field reference'
|
|
3885
|
-
);
|
|
3886
|
-
assert(
|
|
3887
|
-
Object.prototype.hasOwnProperty.call(repaired.pipeline?.[2]?.$sort || {}, '_id.routes.id_location'),
|
|
3888
|
-
'Expected positional $ segment to be removed from sort key'
|
|
3889
|
-
);
|
|
3890
|
-
assert(
|
|
3891
|
-
repaired.pipeline?.[3]?.$project?.route_date === '$routes.date_needed.date',
|
|
3892
|
-
'Expected positional $ segment to be removed from projection field reference'
|
|
3893
|
-
);
|
|
3894
|
-
}
|
|
3895
|
-
|
|
3896
|
-
function testRepairAssistantFieldPathReferenceInPipelineSkipsUndottedPath() {
|
|
3897
|
-
const pipeline = [
|
|
3898
|
-
{
|
|
3899
|
-
$addFields: {
|
|
3900
|
-
volume_unit: '$volume_unit'
|
|
3901
|
-
}
|
|
3902
|
-
}
|
|
3903
|
-
];
|
|
3904
|
-
const repaired = repairAssistantFieldPathReferenceInPipeline(pipeline as any, 'volume_unit');
|
|
3905
|
-
assert(!repaired.changed, 'Expected non-dotted field path to remain unchanged');
|
|
3906
|
-
assert(
|
|
3907
|
-
repaired.pipeline?.[0]?.$addFields?.volume_unit === '$volume_unit',
|
|
3908
|
-
'Expected original reference to be preserved'
|
|
3909
|
-
);
|
|
3910
|
-
}
|
|
3911
|
-
|
|
3912
|
-
function testShouldUseAssistantNavigationFastPath() {
|
|
3913
|
-
assert(
|
|
3914
|
-
shouldUseAssistantNavigationFastPath('What is the route link for invoice list?', false),
|
|
3915
|
-
'Expected route-link request to use navigation fast path'
|
|
3916
|
-
);
|
|
3917
|
-
assert(
|
|
3918
|
-
!shouldUseAssistantNavigationFastPath('How many invoices were paid this month?', false),
|
|
3919
|
-
'Expected data-count request to skip navigation fast path'
|
|
3920
|
-
);
|
|
3921
|
-
assert(
|
|
3922
|
-
!shouldUseAssistantNavigationFastPath('What changed on this page?', false),
|
|
3923
|
-
'Expected change-history prompt to skip navigation fast path'
|
|
3924
|
-
);
|
|
3925
|
-
assert(
|
|
3926
|
-
!shouldUseAssistantNavigationFastPath('How long has the exception list been on this page?', false),
|
|
3927
|
-
'Expected "how long on this page" prompt to skip navigation fast path'
|
|
3928
|
-
);
|
|
3929
|
-
assert(
|
|
3930
|
-
!shouldUseAssistantNavigationFastPath('Why don\'t I see this asset in this dropdown?', false),
|
|
3931
|
-
'Expected bug/issue prompt to skip navigation fast path'
|
|
3932
|
-
);
|
|
3933
|
-
assert(
|
|
3934
|
-
!shouldUseAssistantNavigationFastPath('Route link for invoices', true),
|
|
3935
|
-
'Expected attachments to disable navigation fast path'
|
|
3936
|
-
);
|
|
3937
|
-
}
|
|
3938
|
-
|
|
3939
|
-
function testRankAssistantNavigationRoutes() {
|
|
3940
|
-
const ranked = rankAssistantNavigationRoutes(
|
|
3941
|
-
'Need the invoice list route link',
|
|
3942
|
-
['/dashboard', '/invoice/list', '/invoice/detail', '/chemical/list'],
|
|
3943
|
-
2
|
|
3944
|
-
);
|
|
3945
|
-
assert(ranked.length === 2, 'Expected ranked route limit to apply');
|
|
3946
|
-
assert(ranked[0] === '/invoice/list', 'Expected invoice list route to rank first');
|
|
3947
|
-
}
|
|
3948
|
-
|
|
3949
|
-
function testShouldUseAssistantChangeHistoryFastPath() {
|
|
3950
|
-
assert(
|
|
3951
|
-
shouldUseAssistantChangeHistoryFastPath('What changed recently in GitHub?', false),
|
|
3952
|
-
'Expected change-history prompt to use change-history fast path'
|
|
3953
|
-
);
|
|
3954
|
-
assert(
|
|
3955
|
-
shouldUseAssistantChangeHistoryFastPath('When was the last time the invoice document was updated?', false),
|
|
3956
|
-
'Expected "last updated" prompt to use change-history fast path'
|
|
3957
|
-
);
|
|
3958
|
-
assert(
|
|
3959
|
-
shouldUseAssistantChangeHistoryFastPath('When does this dropdown for lease type get added to this page?', false),
|
|
3960
|
-
'Expected "when does this dropdown get added" prompt to use change-history fast path'
|
|
3961
|
-
);
|
|
3962
|
-
assert(
|
|
3963
|
-
shouldUseAssistantChangeHistoryFastPath('What changed on this page?', false),
|
|
3964
|
-
'Expected "what changed on this page" prompt to use change-history fast path'
|
|
3965
|
-
);
|
|
3966
|
-
assert(
|
|
3967
|
-
shouldUseAssistantChangeHistoryFastPath('What changed on the invoice page?', false),
|
|
3968
|
-
'Expected "what changed on the invoice page" prompt to use change-history fast path'
|
|
3969
|
-
);
|
|
3970
|
-
assert(
|
|
3971
|
-
shouldUseAssistantChangeHistoryFastPath('What did the last update just do?', false),
|
|
3972
|
-
'Expected "what did the last update do" prompt to use change-history fast path'
|
|
3973
|
-
);
|
|
3974
|
-
assert(
|
|
3975
|
-
shouldUseAssistantChangeHistoryFastPath('What did the last system update do?', false),
|
|
3976
|
-
'Expected "what did the last system update do" prompt to use change-history fast path'
|
|
3977
|
-
);
|
|
3978
|
-
assert(
|
|
3979
|
-
shouldUseAssistantChangeHistoryFastPath('How long has this been this way?', false),
|
|
3980
|
-
'Expected duration prompt to use change-history fast path'
|
|
3981
|
-
);
|
|
3982
|
-
assert(
|
|
3983
|
-
shouldUseAssistantChangeHistoryFastPath('What is this new exception list? How long has it been around?', false),
|
|
3984
|
-
'Expected "been around" prompt to use change-history fast path'
|
|
3985
|
-
);
|
|
3986
|
-
assert(
|
|
3987
|
-
shouldUseAssistantChangeHistoryFastPath('How long has the exception list been on this page?', false),
|
|
3988
|
-
'Expected "been on this page" prompt to use change-history fast path'
|
|
3989
|
-
);
|
|
3990
|
-
assert(
|
|
3991
|
-
!shouldUseAssistantChangeHistoryFastPath('How many invoices were paid this month?', false),
|
|
3992
|
-
'Expected quantitative data request to skip change-history fast path'
|
|
3993
|
-
);
|
|
3994
|
-
assert(
|
|
3995
|
-
!shouldUseAssistantChangeHistoryFastPath('What changed recently?', true),
|
|
3996
|
-
'Expected attachments to disable change-history fast path'
|
|
3997
|
-
);
|
|
3998
|
-
}
|
|
3999
|
-
|
|
4000
|
-
function testBuildAssistantChangeHistorySummaryPermissionAware() {
|
|
4001
|
-
const commits = [
|
|
4002
|
-
{
|
|
4003
|
-
date: '2026-02-05T12:00:00.000Z',
|
|
4004
|
-
subject: 'Fix invoice total rounding for customer statements',
|
|
4005
|
-
files: ['server/invoice/report-builder.ts'],
|
|
4006
|
-
modules: ['invoice']
|
|
4007
|
-
},
|
|
4008
|
-
{
|
|
4009
|
-
date: '2026-02-01T12:00:00.000Z',
|
|
4010
|
-
subject: 'Improve work order assignment reliability',
|
|
4011
|
-
files: ['server/work-order/assignment.ts'],
|
|
4012
|
-
modules: ['work_order']
|
|
4013
|
-
}
|
|
4014
|
-
] as any[];
|
|
4015
|
-
|
|
4016
|
-
const invoiceUser = {
|
|
4017
|
-
roles: {
|
|
4018
|
-
super_admin: false,
|
|
4019
|
-
groups: [{ name: 'Accounting', views: ['/invoice/list', '/dashboard'] }],
|
|
4020
|
-
miscs: []
|
|
4021
|
-
},
|
|
4022
|
-
other: {}
|
|
4023
|
-
};
|
|
4024
|
-
const noInvoiceUser = {
|
|
4025
|
-
roles: {
|
|
4026
|
-
super_admin: false,
|
|
4027
|
-
groups: [{ name: 'Field', views: ['/work-order/list'] }],
|
|
4028
|
-
miscs: []
|
|
4029
|
-
},
|
|
4030
|
-
other: {}
|
|
4031
|
-
};
|
|
4032
|
-
|
|
4033
|
-
const detailed = buildAssistantChangeHistorySummaryFromCommits({
|
|
4034
|
-
message: 'What changed in invoices recently?',
|
|
4035
|
-
user: invoiceUser,
|
|
4036
|
-
isSuperAdmin: false,
|
|
4037
|
-
commits,
|
|
4038
|
-
branch: 'main',
|
|
4039
|
-
now: new Date('2026-02-08T00:00:00.000Z')
|
|
4040
|
-
});
|
|
4041
|
-
assert(detailed.reason === 'detailed', 'Expected detailed summary for invoice-permitted user');
|
|
4042
|
-
assert(!detailed.generic, 'Expected detailed summary to be non-generic');
|
|
4043
|
-
assert(detailed.commitCount > 0, 'Expected at least one permitted commit in detailed summary');
|
|
4044
|
-
assert(
|
|
4045
|
-
String(detailed.response || '').includes('Fix invoice total rounding'),
|
|
4046
|
-
'Expected detailed summary to include invoice change subject'
|
|
4047
|
-
);
|
|
4048
|
-
|
|
4049
|
-
const generic = buildAssistantChangeHistorySummaryFromCommits({
|
|
4050
|
-
message: 'What changed in invoices recently?',
|
|
4051
|
-
user: noInvoiceUser,
|
|
4052
|
-
isSuperAdmin: false,
|
|
4053
|
-
commits,
|
|
4054
|
-
branch: 'main',
|
|
4055
|
-
now: new Date('2026-02-08T00:00:00.000Z')
|
|
4056
|
-
});
|
|
4057
|
-
assert(generic.reason === 'generic', 'Expected generic summary for invoice-restricted user');
|
|
4058
|
-
assert(generic.generic, 'Expected generic summary to be marked generic');
|
|
4059
|
-
assert(generic.commitCount === 0, 'Expected generic summary to hide commit details');
|
|
4060
|
-
assert(
|
|
4061
|
-
!String(generic.response || '').includes('Fix invoice total rounding'),
|
|
4062
|
-
'Expected generic summary to redact invoice change subject'
|
|
4063
|
-
);
|
|
4064
|
-
}
|
|
4065
|
-
|
|
4066
|
-
function testBuildAssistantChangeHistorySummaryDuration() {
|
|
4067
|
-
const workOrderUser = {
|
|
4068
|
-
roles: {
|
|
4069
|
-
super_admin: false,
|
|
4070
|
-
groups: [{ name: 'Operations', views: ['/work-order/list'] }],
|
|
4071
|
-
miscs: []
|
|
4072
|
-
},
|
|
4073
|
-
other: {}
|
|
4074
|
-
};
|
|
4075
|
-
const commits = [
|
|
4076
|
-
{
|
|
4077
|
-
date: '2025-11-15T10:00:00.000Z',
|
|
4078
|
-
subject: 'Adjust work order scheduler defaults',
|
|
4079
|
-
files: ['server/work-order/scheduler.ts'],
|
|
4080
|
-
modules: ['work_order']
|
|
4081
|
-
},
|
|
4082
|
-
{
|
|
4083
|
-
date: '2026-02-06T10:00:00.000Z',
|
|
4084
|
-
subject: 'Fix work order dispatch edge case',
|
|
4085
|
-
files: ['server/work-order/dispatch.ts'],
|
|
4086
|
-
modules: ['work_order']
|
|
4087
|
-
}
|
|
4088
|
-
] as any[];
|
|
4089
|
-
const summary = buildAssistantChangeHistorySummaryFromCommits({
|
|
4090
|
-
message: 'How long has this been this way for work orders?',
|
|
4091
|
-
user: workOrderUser,
|
|
4092
|
-
isSuperAdmin: false,
|
|
4093
|
-
commits,
|
|
4094
|
-
branch: 'main',
|
|
4095
|
-
now: new Date('2026-02-08T00:00:00.000Z')
|
|
4096
|
-
});
|
|
4097
|
-
assert(summary.reason === 'detailed', 'Expected detailed duration summary for permitted module');
|
|
4098
|
-
assert(
|
|
4099
|
-
String(summary.response || '').includes('since at least 2025-11-15'),
|
|
4100
|
-
'Expected duration summary to include oldest known date'
|
|
4101
|
-
);
|
|
4102
|
-
}
|
|
4103
|
-
|
|
4104
|
-
function testBuildAssistantChangeHistorySummaryFeatureKeywordScoping() {
|
|
4105
|
-
const commits = [
|
|
4106
|
-
{
|
|
4107
|
-
date: '2026-02-08T12:00:00.000Z',
|
|
4108
|
-
subject: 'Improve deploy queue resiliency for worker restarts',
|
|
4109
|
-
files: ['server/deploy/queue.ts'],
|
|
4110
|
-
modules: ['general']
|
|
4111
|
-
},
|
|
4112
|
-
{
|
|
4113
|
-
date: '2025-09-01T12:00:00.000Z',
|
|
4114
|
-
subject: 'Add exception list filtering for deploy instances',
|
|
4115
|
-
files: ['resolveio/angular/app/dashboard/deploy-instance/deploy-instances.component.ts'],
|
|
4116
|
-
modules: ['general']
|
|
4117
|
-
},
|
|
4118
|
-
{
|
|
4119
|
-
date: '2024-01-15T12:00:00.000Z',
|
|
4120
|
-
subject: 'Migrate logging pipeline internals',
|
|
4121
|
-
files: ['server/logging/pipeline.ts'],
|
|
4122
|
-
modules: ['internal']
|
|
4123
|
-
}
|
|
4124
|
-
] as any[];
|
|
4125
|
-
|
|
4126
|
-
const summary = buildAssistantChangeHistorySummaryFromCommits({
|
|
4127
|
-
message: 'What is this new exception list? How long has it been around?',
|
|
4128
|
-
user: { roles: { super_admin: true }, other: {} },
|
|
4129
|
-
isSuperAdmin: true,
|
|
4130
|
-
commits,
|
|
4131
|
-
branch: 'main',
|
|
4132
|
-
now: new Date('2026-02-08T00:00:00.000Z')
|
|
4133
|
-
});
|
|
4134
|
-
assert(summary.reason === 'detailed', 'Expected detailed summary for super admin');
|
|
4135
|
-
assert(
|
|
4136
|
-
String(summary.response || '').includes('since at least 2025-09-01'),
|
|
4137
|
-
'Expected duration summary to scope oldest date to exception-list related history'
|
|
4138
|
-
);
|
|
4139
|
-
assert(
|
|
4140
|
-
!String(summary.response || '').includes('since at least 2024-01-15'),
|
|
4141
|
-
'Expected duration summary to avoid unrelated older commit history'
|
|
4142
|
-
);
|
|
4143
|
-
}
|
|
4144
|
-
|
|
4145
|
-
function testBuildAssistantChangeHistorySummaryLibraryRepoKeepsDatesButRedactsDetails() {
|
|
4146
|
-
const commits = [
|
|
4147
|
-
{
|
|
4148
|
-
date: '2026-01-15T12:00:00.000Z',
|
|
4149
|
-
subject: 'Refactor invoice aggregation fallback logic',
|
|
4150
|
-
files: ['src/methods/ai-terminal.ts'],
|
|
4151
|
-
modules: ['invoice']
|
|
4152
|
-
}
|
|
4153
|
-
] as any[];
|
|
4154
|
-
const summary = buildAssistantChangeHistorySummaryFromCommits({
|
|
4155
|
-
message: 'What changed recently in invoices?',
|
|
4156
|
-
user: { roles: { super_admin: true }, other: {} },
|
|
4157
|
-
isSuperAdmin: true,
|
|
4158
|
-
commits,
|
|
4159
|
-
branch: 'main',
|
|
4160
|
-
repositoryName: 'resolveio-server-lib',
|
|
4161
|
-
now: new Date('2026-02-08T00:00:00.000Z')
|
|
4162
|
-
});
|
|
4163
|
-
assert(summary.reason === 'detailed', 'Expected detailed summary to remain available for library repos');
|
|
4164
|
-
assert(
|
|
4165
|
-
String(summary.response || '').includes('2026-01-15'),
|
|
4166
|
-
'Expected library summary to retain date visibility'
|
|
4167
|
-
);
|
|
4168
|
-
assert(
|
|
4169
|
-
!String(summary.response || '').includes('Refactor invoice aggregation fallback logic'),
|
|
4170
|
-
'Expected library summary to redact low-level implementation details'
|
|
4171
|
-
);
|
|
4172
|
-
assert(
|
|
4173
|
-
String(summary.response || '').includes('Platform/library update'),
|
|
4174
|
-
'Expected library summary to use high-level library wording'
|
|
4175
|
-
);
|
|
4176
|
-
}
|
|
4177
|
-
|
|
4178
|
-
async function testResolveAssistantReportBuilderBridgeCollectionModeAndPath() {
|
|
4179
|
-
const originalGetMongoManager = (ResolveIOServer as any).getMongoManager;
|
|
4180
|
-
try {
|
|
4181
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4182
|
-
collection: (name: string) => (name === 'report-orders' ? { useRB: true } : {})
|
|
4183
|
-
});
|
|
4184
|
-
const rbPreferred = await resolveAssistantReportBuilderBridgeCollection('orders');
|
|
4185
|
-
assert(rbPreferred.collection === 'report-orders', 'Expected report collection selection when useRB is available');
|
|
4186
|
-
assert(rbPreferred.mode === 'report-builder', 'Expected report-builder mode when useRB is available');
|
|
4187
|
-
assert(
|
|
4188
|
-
Array.isArray(rbPreferred.resolutionPath)
|
|
4189
|
-
&& rbPreferred.resolutionPath.some(step => String(step).includes('manager.useRB:report-orders')),
|
|
4190
|
-
'Expected bridge resolution path to include manager.useRB step'
|
|
4191
|
-
);
|
|
4192
|
-
|
|
4193
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4194
|
-
collection: (_name: string) => ({})
|
|
4195
|
-
});
|
|
4196
|
-
const fakeDb = {
|
|
4197
|
-
listCollections: (filter?: Record<string, any>) => ({
|
|
4198
|
-
toArray: async () => {
|
|
4199
|
-
const name = String(filter?.name || '');
|
|
4200
|
-
if (!name) {
|
|
4201
|
-
return [{ name: 'orders' }];
|
|
4202
|
-
}
|
|
4203
|
-
return name === 'orders' ? [{ name: 'orders' }] : [];
|
|
4204
|
-
}
|
|
4205
|
-
})
|
|
4206
|
-
};
|
|
4207
|
-
const directFallback = await resolveAssistantReportBuilderBridgeCollection('orders', fakeDb as any, 'resolveio');
|
|
4208
|
-
assert(directFallback.collection === 'orders', 'Expected direct fallback to keep base collection');
|
|
4209
|
-
assert(directFallback.mode === 'direct-mongo', 'Expected direct-mongo mode when RB is unavailable');
|
|
4210
|
-
assert(
|
|
4211
|
-
Array.isArray(directFallback.resolutionPath)
|
|
4212
|
-
&& directFallback.resolutionPath.some(step => String(step).includes('db.exists:orders')),
|
|
4213
|
-
'Expected bridge resolution path to include db existence fallback'
|
|
4214
|
-
);
|
|
4215
|
-
}
|
|
4216
|
-
finally {
|
|
4217
|
-
(ResolveIOServer as any).getMongoManager = originalGetMongoManager;
|
|
4218
|
-
}
|
|
4219
|
-
}
|
|
4220
|
-
|
|
4221
|
-
async function testResolveAssistantReportBuilderBridgeCollectionPreservesConfiguredCollection() {
|
|
4222
|
-
const originalGetMongoManager = (ResolveIOServer as any).getMongoManager;
|
|
4223
|
-
try {
|
|
4224
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4225
|
-
collections: () => [
|
|
4226
|
-
{ collectionName: 'work-order-dynamics' },
|
|
4227
|
-
{ collectionName: 'production-sales-orders' }
|
|
4228
|
-
],
|
|
4229
|
-
collection: (_name: string) => ({})
|
|
4230
|
-
});
|
|
4231
|
-
const fakeDb = {
|
|
4232
|
-
listCollections: (filter?: Record<string, any>) => ({
|
|
4233
|
-
toArray: async () => {
|
|
4234
|
-
const name = String(filter?.name || '');
|
|
4235
|
-
if (!name) {
|
|
4236
|
-
return [{ name: 'production-sales-orders' }];
|
|
4237
|
-
}
|
|
4238
|
-
return name === 'production-sales-orders' ? [{ name }] : [];
|
|
4239
|
-
}
|
|
4240
|
-
})
|
|
4241
|
-
};
|
|
4242
|
-
const resolved = await resolveAssistantReportBuilderBridgeCollection(
|
|
4243
|
-
'work-order-dynamics',
|
|
4244
|
-
fakeDb as any,
|
|
4245
|
-
'resolveio',
|
|
4246
|
-
{
|
|
4247
|
-
requestHints: {
|
|
4248
|
-
collectionHints: ['work-order-dynamics', 'production-sales-orders'],
|
|
4249
|
-
rankedCollections: ['production-sales-orders', 'work-order-dynamics']
|
|
4250
|
-
} as any
|
|
4251
|
-
}
|
|
4252
|
-
);
|
|
4253
|
-
assert(
|
|
4254
|
-
resolved.collection === 'work-order-dynamics',
|
|
4255
|
-
'Expected configured work-order collection to win over neighboring DB-listed collections'
|
|
4256
|
-
);
|
|
4257
|
-
assert(resolved.mode === 'direct-mongo', 'Expected configured collection fallback to use direct-mongo mode');
|
|
4258
|
-
assert(
|
|
4259
|
-
Array.isArray(resolved.resolutionPath)
|
|
4260
|
-
&& resolved.resolutionPath.some(step => String(step).includes('manager.configured:work-order-dynamics')),
|
|
4261
|
-
'Expected bridge resolution path to include manager.configured step'
|
|
4262
|
-
);
|
|
4263
|
-
}
|
|
4264
|
-
finally {
|
|
4265
|
-
(ResolveIOServer as any).getMongoManager = originalGetMongoManager;
|
|
4266
|
-
}
|
|
4267
|
-
}
|
|
4268
|
-
|
|
4269
|
-
async function testResolveAssistantReportBuilderBridgeCollectionPreservesPrimaryHintCollection() {
|
|
4270
|
-
const originalGetMongoManager = (ResolveIOServer as any).getMongoManager;
|
|
4271
|
-
try {
|
|
4272
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4273
|
-
collections: () => [],
|
|
4274
|
-
collection: (_name: string) => ({})
|
|
4275
|
-
});
|
|
4276
|
-
const fakeDb = {
|
|
4277
|
-
listCollections: (filter?: Record<string, any>) => ({
|
|
4278
|
-
toArray: async () => {
|
|
4279
|
-
const name = String(filter?.name || '');
|
|
4280
|
-
if (!name) {
|
|
4281
|
-
return [{ name: 'production-sales-orders' }];
|
|
4282
|
-
}
|
|
4283
|
-
return name === 'production-sales-orders' ? [{ name }] : [];
|
|
4284
|
-
}
|
|
4285
|
-
})
|
|
4286
|
-
};
|
|
4287
|
-
const resolved = await resolveAssistantReportBuilderBridgeCollection(
|
|
4288
|
-
'work-order-dynamics',
|
|
4289
|
-
fakeDb as any,
|
|
4290
|
-
'resolveio',
|
|
4291
|
-
{
|
|
4292
|
-
requestHints: {
|
|
4293
|
-
collectionHints: ['work-order-dynamics', 'jobs', 'production-sales-orders'],
|
|
4294
|
-
rankedCollections: ['production-sales-orders', 'work-order-dynamics']
|
|
4295
|
-
} as any
|
|
4296
|
-
}
|
|
4297
|
-
);
|
|
4298
|
-
assert(
|
|
4299
|
-
resolved.collection === 'work-order-dynamics',
|
|
4300
|
-
'Expected primary collection hint to prevent bridge fallback to neighboring collections'
|
|
4301
|
-
);
|
|
4302
|
-
assert(
|
|
4303
|
-
Array.isArray(resolved.resolutionPath)
|
|
4304
|
-
&& resolved.resolutionPath.some(step => String(step).includes('hints.primary:work-order-dynamics')),
|
|
4305
|
-
'Expected bridge resolution path to include primary hint preservation'
|
|
4306
|
-
);
|
|
4307
|
-
}
|
|
4308
|
-
finally {
|
|
4309
|
-
(ResolveIOServer as any).getMongoManager = originalGetMongoManager;
|
|
4310
|
-
}
|
|
4311
|
-
}
|
|
4312
|
-
|
|
4313
|
-
async function testResolveAssistantCollectionNameMergesConfiguredCollectionsIntoFreshCache() {
|
|
4314
|
-
const originalGetMongoManager = (ResolveIOServer as any).getMongoManager;
|
|
4315
|
-
const fakeDb = {
|
|
4316
|
-
listCollections: (filter?: Record<string, any>) => ({
|
|
4317
|
-
toArray: async () => {
|
|
4318
|
-
const name = String(filter?.name || '');
|
|
4319
|
-
if (!name) {
|
|
4320
|
-
return [{ name: 'production-sales-orders' }];
|
|
4321
|
-
}
|
|
4322
|
-
return name === 'production-sales-orders' ? [{ name }] : [];
|
|
4323
|
-
}
|
|
4324
|
-
})
|
|
4325
|
-
};
|
|
4326
|
-
try {
|
|
4327
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4328
|
-
collections: () => [],
|
|
4329
|
-
collection: (_name: string) => ({})
|
|
4330
|
-
});
|
|
4331
|
-
const dbName = 'resolveio-cache-test-configured-collections';
|
|
4332
|
-
const cachedBeforeManager = await resolveAssistantCollectionNameForTesting(
|
|
4333
|
-
fakeDb as any,
|
|
4334
|
-
dbName,
|
|
4335
|
-
'work-order-dynamics',
|
|
4336
|
-
'snf'
|
|
4337
|
-
);
|
|
4338
|
-
assert(
|
|
4339
|
-
cachedBeforeManager.name === 'work-order-dynamics'
|
|
4340
|
-
&& cachedBeforeManager.matched === false
|
|
4341
|
-
&& cachedBeforeManager.candidates.includes('production-sales-orders'),
|
|
4342
|
-
'Expected initial sparse DB cache to miss configured work-order collection while retaining neighboring candidates'
|
|
4343
|
-
);
|
|
4344
|
-
|
|
4345
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4346
|
-
collections: () => [
|
|
4347
|
-
{ collectionName: 'work-order-dynamics' },
|
|
4348
|
-
{ collectionName: 'production-sales-orders' }
|
|
4349
|
-
],
|
|
4350
|
-
collection: (_name: string) => ({})
|
|
4351
|
-
});
|
|
4352
|
-
const resolvedAfterManager = await resolveAssistantCollectionNameForTesting(
|
|
4353
|
-
fakeDb as any,
|
|
4354
|
-
dbName,
|
|
4355
|
-
'work-order-dynamics',
|
|
4356
|
-
'snf'
|
|
4357
|
-
);
|
|
4358
|
-
assert(
|
|
4359
|
-
resolvedAfterManager.name === 'work-order-dynamics',
|
|
4360
|
-
'Expected configured manager collection to be merged into fresh collection cache'
|
|
4361
|
-
);
|
|
4362
|
-
assert(resolvedAfterManager.matched === true, 'Expected configured collection to be treated as matched');
|
|
4363
|
-
}
|
|
4364
|
-
finally {
|
|
4365
|
-
(ResolveIOServer as any).getMongoManager = originalGetMongoManager;
|
|
4366
|
-
}
|
|
4367
|
-
}
|
|
4368
|
-
|
|
4369
|
-
async function testResolveAssistantReportBuilderBridgeCollectionUsesBlendHintsWithoutCrossDomainFallback() {
|
|
4370
|
-
const originalGetMongoManager = (ResolveIOServer as any).getMongoManager;
|
|
4371
|
-
const buildFakeDb = (collections: string[]) => ({
|
|
4372
|
-
listCollections: (filter?: Record<string, any>) => ({
|
|
4373
|
-
toArray: async () => {
|
|
4374
|
-
const name = String(filter?.name || '');
|
|
4375
|
-
if (!name) {
|
|
4376
|
-
return collections.map(collectionName => ({ name: collectionName }));
|
|
4377
|
-
}
|
|
4378
|
-
return collections.includes(name) ? [{ name }] : [];
|
|
4379
|
-
}
|
|
4380
|
-
})
|
|
4381
|
-
});
|
|
4382
|
-
const blendHints = {
|
|
4383
|
-
collectionHints: ['chemical-blends', 'support-tickets'],
|
|
4384
|
-
rankedCollections: ['support-tickets', 'chemical-blends'],
|
|
4385
|
-
fieldHints: ['Field hints (chemical-blends): blend_name, chemical_recipe_quantity'],
|
|
4386
|
-
schemaHints: ['Schema hints (chemical-blends): fields: blend_name, quantity']
|
|
4387
|
-
};
|
|
4388
|
-
try {
|
|
4389
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4390
|
-
collection: (_name: string) => ({})
|
|
4391
|
-
});
|
|
4392
|
-
|
|
4393
|
-
const resolvedBlend = await resolveAssistantReportBuilderBridgeCollection(
|
|
4394
|
-
'blend-tickets',
|
|
4395
|
-
buildFakeDb(['support-tickets', 'chemical-blends']) as any,
|
|
4396
|
-
'resolveio',
|
|
4397
|
-
{ requestHints: blendHints as any }
|
|
4398
|
-
);
|
|
4399
|
-
assert(
|
|
4400
|
-
resolvedBlend.collection === 'chemical-blends',
|
|
4401
|
-
'Expected blend hints to resolve bridge collection to chemical-blends when available'
|
|
4402
|
-
);
|
|
4403
|
-
assert(
|
|
4404
|
-
resolvedBlend.mode === 'direct-mongo',
|
|
4405
|
-
'Expected blend hint fallback to use direct-mongo mode when RB is unavailable'
|
|
4406
|
-
);
|
|
4407
|
-
|
|
4408
|
-
let threwMissingBlend = false;
|
|
4409
|
-
try {
|
|
4410
|
-
await resolveAssistantReportBuilderBridgeCollection(
|
|
4411
|
-
'blend-tickets',
|
|
4412
|
-
buildFakeDb(['support-tickets']) as any,
|
|
4413
|
-
'resolveio',
|
|
4414
|
-
{ requestHints: blendHints as any }
|
|
4415
|
-
);
|
|
4416
|
-
}
|
|
4417
|
-
catch (error: any) {
|
|
4418
|
-
threwMissingBlend = String(error?.message || '')
|
|
4419
|
-
.includes('No queryable collection could be resolved');
|
|
4420
|
-
}
|
|
4421
|
-
assert(
|
|
4422
|
-
threwMissingBlend,
|
|
4423
|
-
'Expected blend ticket requests to fail when no blend collection exists'
|
|
4424
|
-
);
|
|
4425
|
-
}
|
|
4426
|
-
finally {
|
|
4427
|
-
(ResolveIOServer as any).getMongoManager = originalGetMongoManager;
|
|
4428
|
-
}
|
|
4429
|
-
}
|
|
4430
|
-
|
|
4431
|
-
function testBuildAssistantSchemaHintsIncludesFieldsAndLookups() {
|
|
4432
|
-
const originalGetMongoManager = (ResolveIOServer as any).getMongoManager;
|
|
4433
|
-
try {
|
|
4434
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4435
|
-
collection: (name: string) => {
|
|
4436
|
-
if (name !== 'work-orders') {
|
|
4437
|
-
return {};
|
|
4438
|
-
}
|
|
4439
|
-
return {
|
|
4440
|
-
simplschema: {
|
|
4441
|
-
schema: () => ({
|
|
4442
|
-
status: { type: String },
|
|
4443
|
-
date_created: { type: Date },
|
|
4444
|
-
id_customer: { type: String }
|
|
4445
|
-
})
|
|
4446
|
-
},
|
|
4447
|
-
rbSchema: {
|
|
4448
|
-
id_customer: {
|
|
4449
|
-
lookup_collection: 'customers',
|
|
4450
|
-
local_key: 'id_customer',
|
|
4451
|
-
lookup_key: '_id'
|
|
4452
|
-
}
|
|
4453
|
-
}
|
|
4454
|
-
};
|
|
4455
|
-
}
|
|
4456
|
-
});
|
|
4457
|
-
const hints = buildAssistantSchemaHints(['work-orders'], {
|
|
4458
|
-
maxCollections: 1,
|
|
4459
|
-
maxFields: 8,
|
|
4460
|
-
maxLookups: 4
|
|
4461
|
-
});
|
|
4462
|
-
assert(hints.length === 1, 'Expected a single schema hint line');
|
|
4463
|
-
assert(hints[0].includes('Schema hints (work-orders):'), 'Expected schema hint collection label');
|
|
4464
|
-
assert(hints[0].includes('fields:'), 'Expected schema fields section');
|
|
4465
|
-
assert(hints[0].includes('report-builder lookups:'), 'Expected lookup section');
|
|
4466
|
-
assert(
|
|
4467
|
-
hints[0].includes('id_customer->customers._id'),
|
|
4468
|
-
'Expected lookup mapping details in schema hints'
|
|
4469
|
-
);
|
|
4470
|
-
}
|
|
4471
|
-
finally {
|
|
4472
|
-
(ResolveIOServer as any).getMongoManager = originalGetMongoManager;
|
|
4473
|
-
}
|
|
4474
|
-
}
|
|
4475
|
-
|
|
4476
|
-
async function testBuildAssistantToolRequestHintsAndReadDebugBridgeResolution() {
|
|
4477
|
-
const request = buildAssistantToolRequest(
|
|
4478
|
-
{
|
|
4479
|
-
type: 'read',
|
|
4480
|
-
payload: {
|
|
4481
|
-
collection: 'orders',
|
|
4482
|
-
query: { status: 'Open' }
|
|
4483
|
-
},
|
|
4484
|
-
cleaned: '',
|
|
4485
|
-
rawLine: 'REPORT_BUILDER_READ: {"collection":"orders"}'
|
|
4486
|
-
} as any,
|
|
4487
|
-
{
|
|
4488
|
-
id_client: 'client-1',
|
|
4489
|
-
mongo: {
|
|
4490
|
-
database: 'resolveio',
|
|
4491
|
-
databases: ['resolveio'],
|
|
4492
|
-
access: 'read'
|
|
4493
|
-
}
|
|
4494
|
-
} as any,
|
|
4495
|
-
{
|
|
4496
|
-
collectionHints: ['orders', 'orders'],
|
|
4497
|
-
rankedCollections: ['orders', 'report-orders'],
|
|
4498
|
-
fieldHints: ['Field hints (orders): status', 'Field hints (orders): status'],
|
|
4499
|
-
schemaHints: ['Schema hints (orders): fields: status', 'Schema hints (orders): fields: status']
|
|
4500
|
-
}
|
|
4501
|
-
);
|
|
4502
|
-
assert(!!request.__assistantHints, 'Expected normalized assistant hints to be attached');
|
|
4503
|
-
assert(
|
|
4504
|
-
Array.isArray(request.__assistantHints.collectionHints)
|
|
4505
|
-
&& request.__assistantHints.collectionHints.length === 1,
|
|
4506
|
-
'Expected collection hints to be deduplicated'
|
|
4507
|
-
);
|
|
4508
|
-
assert(
|
|
4509
|
-
Array.isArray(request.__assistantHints.schemaHints)
|
|
4510
|
-
&& request.__assistantHints.schemaHints.length === 1,
|
|
4511
|
-
'Expected schema hints to be deduplicated'
|
|
4512
|
-
);
|
|
4513
|
-
|
|
4514
|
-
const originalGetMongoManager = (ResolveIOServer as any).getMongoManager;
|
|
4515
|
-
const originalGetMongoConnection = (ResolveIOServer as any).getMongoConnection;
|
|
4516
|
-
const originalGetServerConfig = (ResolveIOServer as any).getServerConfig;
|
|
4517
|
-
const originalUsersCollection = (UserCollection as any).Users;
|
|
4518
|
-
try {
|
|
4519
|
-
(UserCollection as any).Users = {
|
|
4520
|
-
findById: async (_id: string) => ({
|
|
4521
|
-
_id: 'user-1',
|
|
4522
|
-
username: 'admin',
|
|
4523
|
-
roles: {
|
|
4524
|
-
super_admin: true,
|
|
4525
|
-
groups: [],
|
|
4526
|
-
miscs: []
|
|
4527
|
-
},
|
|
4528
|
-
other: {}
|
|
4529
|
-
})
|
|
4530
|
-
};
|
|
4531
|
-
(ResolveIOServer as any).getServerConfig = () => ({
|
|
4532
|
-
DATABASE: 'resolveio'
|
|
4533
|
-
});
|
|
4534
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4535
|
-
getWatchedDatabases: () => ['resolveio'],
|
|
4536
|
-
collection: (_name: string) => ({})
|
|
4537
|
-
});
|
|
4538
|
-
const fakeDb = {
|
|
4539
|
-
listCollections: (filter?: Record<string, any>) => ({
|
|
4540
|
-
toArray: async () => {
|
|
4541
|
-
const name = String(filter?.name || '');
|
|
4542
|
-
if (!name) {
|
|
4543
|
-
return [{ name: 'orders' }];
|
|
4544
|
-
}
|
|
4545
|
-
return name === 'orders' ? [{ name: 'orders' }] : [];
|
|
4546
|
-
}
|
|
4547
|
-
}),
|
|
4548
|
-
collection: (_name: string) => ({
|
|
4549
|
-
find: (_query: Record<string, any>, _options: Record<string, any>) => ({
|
|
4550
|
-
toArray: async () => [{ _id: 'order-1', name: 'Order Alpha', status: 'Open' }]
|
|
4551
|
-
}),
|
|
4552
|
-
countDocuments: async (_query: Record<string, any>) => 1
|
|
4553
|
-
})
|
|
4554
|
-
};
|
|
4555
|
-
(ResolveIOServer as any).getMongoConnection = () => ({
|
|
4556
|
-
db: (_dbName: string) => fakeDb
|
|
4557
|
-
});
|
|
4558
|
-
|
|
4559
|
-
const readResult = await executeAiAssistantMongoRead({
|
|
4560
|
-
collection: 'orders',
|
|
4561
|
-
query: { status: 'Open' },
|
|
4562
|
-
options: { limit: 1 },
|
|
4563
|
-
permissionView: '/order/list',
|
|
4564
|
-
mongo: {
|
|
4565
|
-
database: 'resolveio',
|
|
4566
|
-
databases: ['resolveio'],
|
|
4567
|
-
access: 'read'
|
|
4568
|
-
},
|
|
4569
|
-
__assistantHints: {
|
|
4570
|
-
collectionHints: ['orders'],
|
|
4571
|
-
schemaHints: ['Schema hints (orders): fields: status']
|
|
4572
|
-
}
|
|
4573
|
-
} as any, { id_user: 'user-1' });
|
|
4574
|
-
assert(readResult?.debug?.bridge === 'direct-mongo', 'Expected direct-mongo bridge mode in debug payload');
|
|
4575
|
-
assert(
|
|
4576
|
-
readResult?.debug?.bridgeResolution?.mode === 'direct-mongo',
|
|
4577
|
-
'Expected bridgeResolution mode to be direct-mongo in debug payload'
|
|
4578
|
-
);
|
|
4579
|
-
assert(
|
|
4580
|
-
readResult?.debug?.requestHints?.collectionHints?.[0] === 'orders',
|
|
4581
|
-
'Expected debug payload to include request hint metadata'
|
|
4582
|
-
);
|
|
4583
|
-
}
|
|
4584
|
-
finally {
|
|
4585
|
-
(ResolveIOServer as any).getMongoManager = originalGetMongoManager;
|
|
4586
|
-
(ResolveIOServer as any).getMongoConnection = originalGetMongoConnection;
|
|
4587
|
-
(ResolveIOServer as any).getServerConfig = originalGetServerConfig;
|
|
4588
|
-
(UserCollection as any).Users = originalUsersCollection;
|
|
4589
|
-
}
|
|
4590
|
-
}
|
|
4591
|
-
|
|
4592
|
-
async function testExecuteAiAssistantMongoAggregateFallsBackWithinCollectionForMissingActiveFields() {
|
|
4593
|
-
const originalGetMongoManager = (ResolveIOServer as any).getMongoManager;
|
|
4594
|
-
const originalGetMongoConnection = (ResolveIOServer as any).getMongoConnection;
|
|
4595
|
-
const originalGetServerConfig = (ResolveIOServer as any).getServerConfig;
|
|
4596
|
-
const originalUsersCollection = (UserCollection as any).Users;
|
|
4597
|
-
const aggregateCalls: string[] = [];
|
|
4598
|
-
try {
|
|
4599
|
-
(UserCollection as any).Users = {
|
|
4600
|
-
findById: async (_id: string) => ({
|
|
4601
|
-
_id: 'user-1',
|
|
4602
|
-
username: 'admin',
|
|
4603
|
-
roles: {
|
|
4604
|
-
super_admin: true,
|
|
4605
|
-
groups: [],
|
|
4606
|
-
miscs: []
|
|
4607
|
-
},
|
|
4608
|
-
other: {}
|
|
4609
|
-
})
|
|
4610
|
-
};
|
|
4611
|
-
(ResolveIOServer as any).getServerConfig = () => ({
|
|
4612
|
-
DATABASE: 'resolveio'
|
|
4613
|
-
});
|
|
4614
|
-
(ResolveIOServer as any).getMongoManager = () => ({
|
|
4615
|
-
getWatchedDatabases: () => ['resolveio'],
|
|
4616
|
-
collection: (_name: string) => ({})
|
|
4617
|
-
});
|
|
4618
|
-
const fakeDb = {
|
|
4619
|
-
listCollections: (filter?: Record<string, any>) => ({
|
|
4620
|
-
toArray: async () => {
|
|
4621
|
-
const name = String(filter?.name || '');
|
|
4622
|
-
const collections = ['customers', 'support-tickets'];
|
|
4623
|
-
if (!name) {
|
|
4624
|
-
return collections.map(collectionName => ({ name: collectionName }));
|
|
4625
|
-
}
|
|
4626
|
-
return collections.includes(name) ? [{ name }] : [];
|
|
4627
|
-
}
|
|
4628
|
-
}),
|
|
4629
|
-
collection: (name: string) => ({
|
|
4630
|
-
find: (_query: Record<string, any>) => ({
|
|
4631
|
-
toArray: async () => {
|
|
4632
|
-
if (name === 'customers') {
|
|
4633
|
-
return [{ _id: 'customer-1', name: 'Acme', state: 'Texas' }];
|
|
4634
|
-
}
|
|
4635
|
-
return [];
|
|
4636
|
-
}
|
|
4637
|
-
}),
|
|
4638
|
-
aggregate: (pipeline: Array<Record<string, any>>) => ({
|
|
4639
|
-
toArray: async () => {
|
|
4640
|
-
aggregateCalls.push(name);
|
|
4641
|
-
if (name !== 'customers') {
|
|
4642
|
-
return [{ active_clients: 334 }];
|
|
4643
|
-
}
|
|
4644
|
-
const matchStage = pipeline.find(stage => stage?.$match && typeof stage.$match === 'object');
|
|
4645
|
-
const matchText = JSON.stringify(matchStage?.$match || {});
|
|
4646
|
-
const hasActiveFilters = /\"(status|state|active|is_active|isactive|enabled|is_enabled|isenabled)\"/.test(matchText);
|
|
4647
|
-
return hasActiveFilters ? [] : [{ active_clients: 476 }];
|
|
4648
|
-
}
|
|
4649
|
-
}),
|
|
4650
|
-
countDocuments: async () => 0
|
|
4651
|
-
})
|
|
4652
|
-
};
|
|
4653
|
-
(ResolveIOServer as any).getMongoConnection = () => ({
|
|
4654
|
-
db: (_dbName: string) => fakeDb
|
|
4655
|
-
});
|
|
4656
|
-
|
|
4657
|
-
const aggregateResult = await executeAiAssistantMongoAggregate({
|
|
4658
|
-
collection: 'customers',
|
|
4659
|
-
permissionView: '/client/list',
|
|
4660
|
-
pipeline: [
|
|
4661
|
-
{
|
|
4662
|
-
$match: {
|
|
4663
|
-
$and: [
|
|
4664
|
-
{
|
|
4665
|
-
$or: [
|
|
4666
|
-
{ active: true },
|
|
4667
|
-
{ is_active: true },
|
|
4668
|
-
{ isactive: true },
|
|
4669
|
-
{ enabled: true },
|
|
4670
|
-
{ is_enabled: true },
|
|
4671
|
-
{ isenabled: true },
|
|
4672
|
-
{ status: { $regex: '^active$', $options: 'i' } },
|
|
4673
|
-
{ state: { $regex: '^active$', $options: 'i' } }
|
|
4674
|
-
]
|
|
4675
|
-
}
|
|
4676
|
-
]
|
|
4677
|
-
}
|
|
4678
|
-
},
|
|
4679
|
-
{
|
|
4680
|
-
$group: {
|
|
4681
|
-
_id: null,
|
|
4682
|
-
active_clients: { $sum: 1 }
|
|
4683
|
-
}
|
|
4684
|
-
},
|
|
4685
|
-
{
|
|
4686
|
-
$project: {
|
|
4687
|
-
_id: 0,
|
|
4688
|
-
active_clients: 1
|
|
4689
|
-
}
|
|
4690
|
-
}
|
|
4691
|
-
],
|
|
4692
|
-
options: {
|
|
4693
|
-
allowDiskUse: true,
|
|
4694
|
-
limit: 1
|
|
4695
|
-
},
|
|
4696
|
-
mongo: {
|
|
4697
|
-
database: 'resolveio',
|
|
4698
|
-
databases: ['resolveio'],
|
|
4699
|
-
access: 'read'
|
|
4700
|
-
}
|
|
4701
|
-
} as any, { id_user: 'user-1' });
|
|
4702
|
-
|
|
4703
|
-
assert(
|
|
4704
|
-
Array.isArray(aggregateResult?.documents) && aggregateResult.documents[0]?.active_clients === 476,
|
|
4705
|
-
'Expected missing active-field aggregate fallback to stay on the requested collection and count all matching records'
|
|
4706
|
-
);
|
|
4707
|
-
assert(
|
|
4708
|
-
aggregateResult?.debug?.fallbacks?.activeStatus?.used === true,
|
|
4709
|
-
'Expected aggregate debug payload to record activeStatus fallback usage'
|
|
4710
|
-
);
|
|
4711
|
-
assert(
|
|
4712
|
-
!aggregateCalls.includes('support-tickets'),
|
|
4713
|
-
'Expected active-status aggregate fallback to avoid cross-collection retry'
|
|
4714
|
-
);
|
|
4715
|
-
}
|
|
4716
|
-
finally {
|
|
4717
|
-
(ResolveIOServer as any).getMongoManager = originalGetMongoManager;
|
|
4718
|
-
(ResolveIOServer as any).getMongoConnection = originalGetMongoConnection;
|
|
4719
|
-
(ResolveIOServer as any).getServerConfig = originalGetServerConfig;
|
|
4720
|
-
(UserCollection as any).Users = originalUsersCollection;
|
|
4721
|
-
}
|
|
4722
|
-
}
|
|
4723
|
-
|
|
4724
|
-
async function run() {
|
|
4725
|
-
testDirectiveParsing();
|
|
4726
|
-
testReportBuilderDirectiveParsing();
|
|
4727
|
-
testFlattenForTable();
|
|
4728
|
-
testDisplayTableDefaults();
|
|
4729
|
-
testSerializeMongoValue();
|
|
4730
|
-
testDisplayMarkdownTable();
|
|
4731
|
-
testAssistantToolFallbackResponseIncludesUsefulLeadSummary();
|
|
4732
|
-
testAssistantToolFallbackResponseIncludesRequestedBreakdowns();
|
|
4733
|
-
testAssistantToolFallbackResponseExplainsZeroRows();
|
|
4734
|
-
testBuildAssistantAnswerQualityFromExecution();
|
|
4735
|
-
testAssistantZeroRowDisplayDoesNotAppendSyntheticTable();
|
|
4736
|
-
testDisplayMarkdownTableDefaultsToShortDateFormat();
|
|
4737
|
-
testDeriveAssistantCommandExecutionStatus();
|
|
4738
|
-
testResolveCodexThoughtLevelPolicy();
|
|
4739
|
-
testAssistantSystemPromptAllowsWorkspaceDebuggingAndFixes();
|
|
4740
|
-
testAssistantGuardrailsAllowInternalEngineeringRequests();
|
|
4741
|
-
testBuildAssistantWorkspaceRootCandidates();
|
|
4742
|
-
testShouldRunAssistantPlannerPolicy();
|
|
4743
|
-
testAssistantAppHeuristicRegistryTermHintsAndDeterministicEnablement();
|
|
4744
|
-
testAssistantAppFieldSynonymsStayAppOwned();
|
|
4745
|
-
testAssistantAppHeuristicRegistryLoadsRelativeFileFromParityServerDir();
|
|
4746
|
-
testAssistantCoreHeuristicsStayPlatformScoped();
|
|
4747
|
-
testAssistantAppHeuristicsCanOwnDomainCollectionHints();
|
|
4748
|
-
testAssistantClassifierWorkflowArbitrationPrefersExplicitWorkOrders();
|
|
4749
|
-
testAssistantAppDataIntentsResolveSnfDomainRequests();
|
|
4750
|
-
testAssistantAppDataIntentContractsPreserveGenericBreakdownsAndDateRanges();
|
|
4751
|
-
testAssistantRemovedDomainHeuristicIdsRemainAppSpecific();
|
|
4752
|
-
testAssistantHeuristicBlendIdsRemainAppSpecific();
|
|
4753
|
-
testShouldEnforceAssistantDatedDirectiveSkipsWindowedRankingRequests();
|
|
4754
|
-
testShouldEnforceAssistantDatedDirectiveForExplicitTemporalBreakdown();
|
|
4755
|
-
testShouldEnforceAssistantDatedDirectiveForPlannerTemporalGroupBy();
|
|
4756
|
-
testBuildAssistantDatedPivotDisplayWithYearMonthAndCustomer();
|
|
4757
|
-
testBuildAssistantDatedPivotDisplayWithoutDimensionCreatesMetricGrid();
|
|
4758
|
-
testBuildAssistantInvoiceCustomerLabelExprIncludesClientFallbacks();
|
|
4759
|
-
testResolveAssistantReadDisplayMaxRows();
|
|
4760
|
-
testResolveAssistantCollectionOverrideKeepsSpecificOrdersCollection();
|
|
4761
|
-
testResolveAssistantCollectionOverrideStillAppliesForUnrelatedCollection();
|
|
4762
|
-
testResolveAssistantCollectionOverrideSkipsBorderlineGapForWorkOrderRanking();
|
|
4763
|
-
testResolveAssistantCollectionOverrideKeepsWorkOrderCollectionWhenOrdersScoresHigher();
|
|
4764
|
-
testResolveCollectionOverrideWithContextRespectsPermissionRouteHints();
|
|
4765
|
-
testResolveCollectionOverrideWithContextUsesSpecificPermissionRouteCollection();
|
|
4766
|
-
testResolveCollectionOverrideWithContextDoesNotRewriteAggregateDataPlanToRouteCollection();
|
|
4767
|
-
testResolveCollectionOverrideWithContextUsesOrderedCollectionHints();
|
|
4768
|
-
testResolveCollectionOverrideWithContextPreservesPrimaryCollectionHint();
|
|
4769
|
-
testResolveCollectionOverrideWithContextKeepsRouteMatchedJobsOverChemicalHint();
|
|
4770
|
-
testResolveCollectionOverrideWithContextPreservesJobDomainDetailCollections();
|
|
4771
|
-
testResolveCollectionOverrideWithContextKeepsCompetitiveRootCollectionHint();
|
|
4772
|
-
testShouldPreserveAssistantProbeCollectionForPrimaryHintAndRoute();
|
|
4773
|
-
testResolveCollectionOverrideWithContextStillOverridesWithoutRouteHints();
|
|
4774
|
-
testResolveAssistantCrossCollectionFallbackCandidatesForOrders();
|
|
4775
|
-
testResolveAssistantCrossCollectionFallbackCandidatesForWorkOrderDynamics();
|
|
4776
|
-
testResolveAssistantCrossCollectionFallbackCandidatesForUnknownCollection();
|
|
4777
|
-
testResolveAssistantCrossCollectionFallbackCandidatesForTimeEntries();
|
|
4778
|
-
testResolveAssistantAvailableCrossCollectionFallbacksFromNamesUsesSimilarityRanking();
|
|
4779
|
-
testResolveAssistantAvailableCrossCollectionFallbacksFromNamesSkipsCurrentFamilyOnlyMatch();
|
|
4780
|
-
testResolveAssistantCrossCollectionFallbackCandidatesForSupportAlias();
|
|
4781
|
-
testShouldAcceptAssistantFallbackDocumentsRejectsBlankDimensionRows();
|
|
4782
|
-
testShouldAcceptAssistantFallbackDocumentsAcceptsNamedDimensions();
|
|
4783
|
-
testShouldAcceptAssistantFallbackDocumentsAcceptsMetricOnlyRows();
|
|
4784
|
-
testDisplayMarkdownInvoiceNumbersAndCountsStayNumeric();
|
|
4785
|
-
testDisplayMarkdownCurrencyColumnsStillUseCurrencyFormat();
|
|
4786
|
-
testDisplayMarkdownKeepsAlphanumericBlendAndChemicalNames();
|
|
4787
|
-
testDisplayTableCoalescesStringVariantFields();
|
|
4788
|
-
testDisplayTableCoalescesEquivalentCreatedDateFields();
|
|
4789
|
-
testDisplayTableKeepsDistinctCreatedDateAndDateFields();
|
|
4790
|
-
testStripQueryFieldPathsDeep();
|
|
4791
|
-
testResolveReadMultiTermJobRegexFallbackForTesting();
|
|
4792
|
-
testNormalizeIdsForTargetField();
|
|
4793
|
-
testRewriteEmbeddedMatchObjects();
|
|
4794
|
-
testRewriteMatchExpressionsToExpr();
|
|
4795
|
-
testRewriteMatchExpressionsToExprCompactNowToken();
|
|
4796
|
-
testNormalizeAssistantNowExprPlaceholdersDateArithmeticArgs();
|
|
4797
|
-
testNormalizeAssistantNowExprPlaceholdersDateArithmeticUnknownKeyAsStartDate();
|
|
4798
|
-
testNormalizeAssistantNowExprPlaceholdersDateArithmeticUnknownKeyAsAmount();
|
|
4799
|
-
testNormalizeAssistantMonthlyCalendarWindowPipelineConvertsRollingMonthlyRange();
|
|
4800
|
-
testNormalizeAssistantMonthlyCalendarWindowPipelineSkipsDailyGrouping();
|
|
4801
|
-
testExpandAggregateDateMatchFallbackExpandsNestedOrClauses();
|
|
4802
|
-
testNormalizeAssistantAggregatePipelineRankSortBySingleKey();
|
|
4803
|
-
testNormalizeAssistantAggregatePipelineMovesLastNLimitBeforeGroup();
|
|
4804
|
-
testNormalizeAssistantAggregatePipelineKeepsTrendGroupingLimitAfterGroup();
|
|
4805
|
-
testBuildAssistantToolRequestRestoresMongoSafeDirectiveKeys();
|
|
4806
|
-
testRepairAssistantPositionalPathSegmentsInPipeline();
|
|
4807
|
-
testRepairAssistantFieldPathReferenceInPipelineUsesGetFieldForDottedPath();
|
|
4808
|
-
testRepairAssistantFieldPathReferenceInPipelineSkipsUndottedPath();
|
|
4809
|
-
testShouldUseAssistantNavigationFastPath();
|
|
4810
|
-
testRankAssistantNavigationRoutes();
|
|
4811
|
-
testShouldUseAssistantChangeHistoryFastPath();
|
|
4812
|
-
testBuildAssistantChangeHistorySummaryPermissionAware();
|
|
4813
|
-
testBuildAssistantChangeHistorySummaryDuration();
|
|
4814
|
-
testBuildAssistantChangeHistorySummaryFeatureKeywordScoping();
|
|
4815
|
-
testBuildAssistantChangeHistorySummaryLibraryRepoKeepsDatesButRedactsDetails();
|
|
4816
|
-
await testResolveAssistantReportBuilderBridgeCollectionModeAndPath();
|
|
4817
|
-
await testResolveAssistantReportBuilderBridgeCollectionPreservesConfiguredCollection();
|
|
4818
|
-
await testResolveAssistantReportBuilderBridgeCollectionPreservesPrimaryHintCollection();
|
|
4819
|
-
await testResolveAssistantCollectionNameMergesConfiguredCollectionsIntoFreshCache();
|
|
4820
|
-
await testResolveAssistantReportBuilderBridgeCollectionUsesBlendHintsWithoutCrossDomainFallback();
|
|
4821
|
-
testBuildAssistantSchemaHintsIncludesFieldsAndLookups();
|
|
4822
|
-
await testBuildAssistantToolRequestHintsAndReadDebugBridgeResolution();
|
|
4823
|
-
await testExecuteAiAssistantMongoAggregateFallsBackWithinCollectionForMissingActiveFields();
|
|
4824
|
-
console.log('ai assistant utils tests passed');
|
|
4825
|
-
}
|
|
4826
|
-
|
|
4827
|
-
run()
|
|
4828
|
-
.then(() => {
|
|
4829
|
-
process.exit(process.exitCode ?? 0);
|
|
4830
|
-
})
|
|
4831
|
-
.catch((error) => {
|
|
4832
|
-
console.error(error);
|
|
4833
|
-
process.exit(1);
|
|
4834
|
-
});
|