@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.
Files changed (745) hide show
  1. package/ai/assistant-core-heuristics.d.ts +11 -0
  2. package/ai/assistant-core-heuristics.js +356 -0
  3. package/ai/assistant-core-heuristics.js.map +1 -0
  4. package/ai/resolveio-platform-intelligence-memory-corpus.d.ts +3 -0
  5. package/ai/resolveio-platform-intelligence-memory-corpus.js +214 -0
  6. package/ai/resolveio-platform-intelligence-memory-corpus.js.map +1 -0
  7. package/ai/resolveio-platform-intelligence-memory.d.ts +20 -0
  8. package/ai/resolveio-platform-intelligence-memory.js +341 -0
  9. package/ai/resolveio-platform-intelligence-memory.js.map +1 -0
  10. package/{src/ai/resolveio-platform-intelligence-types.ts → ai/resolveio-platform-intelligence-types.d.ts} +15 -20
  11. package/ai/resolveio-platform-intelligence-types.js +4 -0
  12. package/ai/resolveio-platform-intelligence-types.js.map +1 -0
  13. package/ai/resolveio-platform-intelligence.d.ts +6 -0
  14. package/ai/resolveio-platform-intelligence.js +463 -0
  15. package/ai/resolveio-platform-intelligence.js.map +1 -0
  16. package/client-server-app.d.ts +1 -0
  17. package/client-server-app.js +68 -0
  18. package/client-server-app.js.map +1 -0
  19. package/collections/ai-run.collection.d.ts +3 -0
  20. package/collections/ai-run.collection.js +170 -0
  21. package/collections/ai-run.collection.js.map +1 -0
  22. package/collections/ai-terminal-conversation.collection.d.ts +2 -0
  23. package/collections/ai-terminal-conversation.collection.js +140 -0
  24. package/collections/ai-terminal-conversation.collection.js.map +1 -0
  25. package/collections/ai-terminal-issue-report.collection.d.ts +2 -0
  26. package/collections/ai-terminal-issue-report.collection.js +148 -0
  27. package/collections/ai-terminal-issue-report.collection.js.map +1 -0
  28. package/collections/ai-terminal-message.collection.d.ts +2 -0
  29. package/collections/ai-terminal-message.collection.js +121 -0
  30. package/collections/ai-terminal-message.collection.js.map +1 -0
  31. package/collections/app-setting.collection.d.ts +3 -0
  32. package/collections/app-setting.collection.js +103 -0
  33. package/collections/app-setting.collection.js.map +1 -0
  34. package/collections/app-status.collection.d.ts +3 -0
  35. package/collections/app-status.collection.js +57 -0
  36. package/collections/app-status.collection.js.map +1 -0
  37. package/collections/communication-metric.collection.d.ts +2 -0
  38. package/collections/communication-metric.collection.js +133 -0
  39. package/collections/communication-metric.collection.js.map +1 -0
  40. package/collections/counter.collection.d.ts +3 -0
  41. package/collections/counter.collection.js +56 -0
  42. package/collections/counter.collection.js.map +1 -0
  43. package/collections/cron-job-history.collection.d.ts +3 -0
  44. package/collections/cron-job-history.collection.js +137 -0
  45. package/collections/cron-job-history.collection.js.map +1 -0
  46. package/collections/cron-job.collection.d.ts +3 -0
  47. package/collections/cron-job.collection.js +92 -0
  48. package/collections/cron-job.collection.js.map +1 -0
  49. package/collections/customer-notification.collection.d.ts +3 -0
  50. package/collections/customer-notification.collection.js +130 -0
  51. package/collections/customer-notification.collection.js.map +1 -0
  52. package/collections/customer-portal-password.collection.d.ts +3 -0
  53. package/collections/customer-portal-password.collection.js +75 -0
  54. package/collections/customer-portal-password.collection.js.map +1 -0
  55. package/collections/email-history.collection.d.ts +3 -0
  56. package/collections/email-history.collection.js +134 -0
  57. package/collections/email-history.collection.js.map +1 -0
  58. package/collections/email-verified.collection.d.ts +3 -0
  59. package/collections/email-verified.collection.js +62 -0
  60. package/collections/email-verified.collection.js.map +1 -0
  61. package/collections/file.collection.d.ts +3 -0
  62. package/collections/file.collection.js +74 -0
  63. package/collections/file.collection.js.map +1 -0
  64. package/collections/flag-update.collection.d.ts +3 -0
  65. package/collections/flag-update.collection.js +57 -0
  66. package/collections/flag-update.collection.js.map +1 -0
  67. package/collections/flag.collection.d.ts +3 -0
  68. package/collections/flag.collection.js +57 -0
  69. package/collections/flag.collection.js.map +1 -0
  70. package/collections/log-method-latency.collection.d.ts +3 -0
  71. package/collections/log-method-latency.collection.js +77 -0
  72. package/collections/log-method-latency.collection.js.map +1 -0
  73. package/collections/log-subscription.collection.d.ts +3 -0
  74. package/collections/log-subscription.collection.js +80 -0
  75. package/collections/log-subscription.collection.js.map +1 -0
  76. package/collections/log.collection.d.ts +3 -0
  77. package/collections/log.collection.js +93 -0
  78. package/collections/log.collection.js.map +1 -0
  79. package/collections/logged-in-users.collection.d.ts +3 -0
  80. package/collections/logged-in-users.collection.js +67 -0
  81. package/collections/logged-in-users.collection.js.map +1 -0
  82. package/collections/monitor-cpu.collection.d.ts +3 -0
  83. package/collections/monitor-cpu.collection.js +65 -0
  84. package/collections/monitor-cpu.collection.js.map +1 -0
  85. package/collections/monitor-function.collection.d.ts +3 -0
  86. package/collections/monitor-function.collection.js +74 -0
  87. package/collections/monitor-function.collection.js.map +1 -0
  88. package/collections/monitor-memory.collection.d.ts +3 -0
  89. package/collections/monitor-memory.collection.js +77 -0
  90. package/collections/monitor-memory.collection.js.map +1 -0
  91. package/collections/monitor-mongo.collection.d.ts +3 -0
  92. package/collections/monitor-mongo.collection.js +71 -0
  93. package/collections/monitor-mongo.collection.js.map +1 -0
  94. package/collections/notification.collection.d.ts +3 -0
  95. package/collections/notification.collection.js +57 -0
  96. package/collections/notification.collection.js.map +1 -0
  97. package/collections/openai-usage-ledger.collection.d.ts +2 -0
  98. package/collections/openai-usage-ledger.collection.js +188 -0
  99. package/collections/openai-usage-ledger.collection.js.map +1 -0
  100. package/collections/report-builder-dashboard-builder.collection.d.ts +3 -0
  101. package/collections/report-builder-dashboard-builder.collection.js +109 -0
  102. package/collections/report-builder-dashboard-builder.collection.js.map +1 -0
  103. package/collections/report-builder-library.collection.d.ts +3 -0
  104. package/collections/report-builder-library.collection.js +87 -0
  105. package/collections/report-builder-library.collection.js.map +1 -0
  106. package/collections/report-builder-report.collection.d.ts +4 -0
  107. package/collections/report-builder-report.collection.js +184 -0
  108. package/collections/report-builder-report.collection.js.map +1 -0
  109. package/collections/user-group.collection.d.ts +4 -0
  110. package/collections/user-group.collection.js +89 -0
  111. package/collections/user-group.collection.js.map +1 -0
  112. package/collections/user-guide.collection.d.ts +3 -0
  113. package/collections/user-guide.collection.js +57 -0
  114. package/collections/user-guide.collection.js.map +1 -0
  115. package/collections/user.collection.d.ts +4 -0
  116. package/collections/user.collection.js +180 -0
  117. package/collections/user.collection.js.map +1 -0
  118. package/cron/cron.d.ts +14 -0
  119. package/cron/cron.js +216 -0
  120. package/cron/cron.js.map +1 -0
  121. package/fixtures/cron-jobs.d.ts +1 -0
  122. package/fixtures/cron-jobs.js +150 -0
  123. package/fixtures/cron-jobs.js.map +1 -0
  124. package/fixtures/init.d.ts +1 -0
  125. package/fixtures/init.js +91 -0
  126. package/fixtures/init.js.map +1 -0
  127. package/http/auth.d.ts +2 -0
  128. package/http/auth.js +951 -0
  129. package/http/auth.js.map +1 -0
  130. package/http/health.d.ts +1 -0
  131. package/http/health.js +11 -0
  132. package/http/health.js.map +1 -0
  133. package/http/home.d.ts +1 -0
  134. package/http/home.js +134 -0
  135. package/http/home.js.map +1 -0
  136. package/http/slow-query-publication.d.ts +2 -0
  137. package/http/slow-query-publication.js +99 -0
  138. package/http/slow-query-publication.js.map +1 -0
  139. package/index.d.ts +1 -0
  140. package/index.js +19 -0
  141. package/index.js.map +1 -0
  142. package/managers/ai-assistant-codex-manager.manager.d.ts +67 -0
  143. package/managers/ai-assistant-codex-manager.manager.js +1113 -0
  144. package/managers/ai-assistant-codex-manager.manager.js.map +1 -0
  145. package/managers/ai-run-evidence.manager.d.ts +36 -0
  146. package/managers/ai-run-evidence.manager.js +377 -0
  147. package/managers/ai-run-evidence.manager.js.map +1 -0
  148. package/managers/communication-metric.manager.d.ts +16 -0
  149. package/managers/communication-metric.manager.js +134 -0
  150. package/managers/communication-metric.manager.js.map +1 -0
  151. package/managers/cron.manager.d.ts +20 -0
  152. package/managers/cron.manager.js +534 -0
  153. package/managers/cron.manager.js.map +1 -0
  154. package/managers/customer-notification-content.manager.d.ts +55 -0
  155. package/managers/customer-notification-content.manager.js +158 -0
  156. package/managers/customer-notification-content.manager.js.map +1 -0
  157. package/managers/diagnostic-manager-bootstrap.d.ts +9 -0
  158. package/managers/diagnostic-manager-bootstrap.js +260 -0
  159. package/managers/diagnostic-manager-bootstrap.js.map +1 -0
  160. package/managers/error-auto-fix.manager.d.ts +149 -0
  161. package/managers/error-auto-fix.manager.js +3064 -0
  162. package/managers/error-auto-fix.manager.js.map +1 -0
  163. package/managers/local-log.manager.d.ts +18 -0
  164. package/managers/local-log.manager.js +88 -0
  165. package/managers/local-log.manager.js.map +1 -0
  166. package/managers/method.manager.d.ts +84 -0
  167. package/managers/method.manager.js +1964 -0
  168. package/managers/method.manager.js.map +1 -0
  169. package/managers/mongo.manager.d.ts +224 -0
  170. package/managers/mongo.manager.js +5000 -0
  171. package/managers/mongo.manager.js.map +1 -0
  172. package/managers/monitor.manager.d.ts +70 -0
  173. package/managers/monitor.manager.js +550 -0
  174. package/managers/monitor.manager.js.map +1 -0
  175. package/managers/openai-usage-ledger.manager.d.ts +30 -0
  176. package/managers/openai-usage-ledger.manager.js +142 -0
  177. package/managers/openai-usage-ledger.manager.js.map +1 -0
  178. package/managers/slow-query-verifier.manager.d.ts +144 -0
  179. package/managers/slow-query-verifier.manager.js +3857 -0
  180. package/managers/slow-query-verifier.manager.js.map +1 -0
  181. package/managers/slow-query.manager.d.ts +28 -0
  182. package/managers/slow-query.manager.js +468 -0
  183. package/managers/slow-query.manager.js.map +1 -0
  184. package/managers/subscription.manager.d.ts +169 -0
  185. package/managers/subscription.manager.js +3434 -0
  186. package/managers/subscription.manager.js.map +1 -0
  187. package/managers/websocket.manager.d.ts +73 -0
  188. package/managers/websocket.manager.js +673 -0
  189. package/managers/websocket.manager.js.map +1 -0
  190. package/managers/worker-dispatcher.manager.d.ts +120 -0
  191. package/managers/worker-dispatcher.manager.js +1266 -0
  192. package/managers/worker-dispatcher.manager.js.map +1 -0
  193. package/managers/worker-server.manager.d.ts +35 -0
  194. package/managers/worker-server.manager.js +582 -0
  195. package/managers/worker-server.manager.js.map +1 -0
  196. package/methods/accounts.d.ts +2 -0
  197. package/methods/accounts.js +624 -0
  198. package/methods/accounts.js.map +1 -0
  199. package/methods/ai-terminal.d.ts +458 -0
  200. package/methods/ai-terminal.js +27991 -0
  201. package/methods/ai-terminal.js.map +1 -0
  202. package/methods/app-settings.d.ts +2 -0
  203. package/methods/app-settings.js +169 -0
  204. package/methods/app-settings.js.map +1 -0
  205. package/methods/aws.d.ts +2 -0
  206. package/methods/aws.js +877 -0
  207. package/methods/aws.js.map +1 -0
  208. package/methods/collections.d.ts +2 -0
  209. package/methods/collections.js +719 -0
  210. package/methods/collections.js.map +1 -0
  211. package/methods/counters.d.ts +2 -0
  212. package/methods/counters.js +113 -0
  213. package/methods/counters.js.map +1 -0
  214. package/methods/cron-jobs.d.ts +2 -0
  215. package/methods/cron-jobs.js +2475 -0
  216. package/methods/cron-jobs.js.map +1 -0
  217. package/methods/customer-notifications.d.ts +2 -0
  218. package/methods/customer-notifications.js +528 -0
  219. package/methods/customer-notifications.js.map +1 -0
  220. package/methods/diagnostics.d.ts +2 -0
  221. package/methods/diagnostics.js +703 -0
  222. package/methods/diagnostics.js.map +1 -0
  223. package/methods/flag-updates.d.ts +2 -0
  224. package/methods/flag-updates.js +8 -0
  225. package/methods/flag-updates.js.map +1 -0
  226. package/methods/flags.d.ts +2 -0
  227. package/methods/flags.js +8 -0
  228. package/methods/flags.js.map +1 -0
  229. package/methods/logs.d.ts +2 -0
  230. package/methods/logs.js +751 -0
  231. package/methods/logs.js.map +1 -0
  232. package/methods/mongo-explorer.d.ts +2 -0
  233. package/methods/mongo-explorer.js +1808 -0
  234. package/methods/mongo-explorer.js.map +1 -0
  235. package/methods/monitor.d.ts +2 -0
  236. package/methods/monitor.js +543 -0
  237. package/methods/monitor.js.map +1 -0
  238. package/methods/pdf.d.ts +2 -0
  239. package/methods/pdf.js +1216 -0
  240. package/methods/pdf.js.map +1 -0
  241. package/methods/publications.d.ts +1 -0
  242. package/methods/publications.js +183 -0
  243. package/methods/publications.js.map +1 -0
  244. package/methods/report-builder.d.ts +2 -0
  245. package/methods/report-builder.js +3094 -0
  246. package/methods/report-builder.js.map +1 -0
  247. package/methods/support.d.ts +2 -0
  248. package/methods/support.js +430 -0
  249. package/methods/support.js.map +1 -0
  250. package/models/ai-run.model.d.ts +19 -0
  251. package/models/ai-run.model.js +4 -0
  252. package/models/ai-run.model.js.map +1 -0
  253. package/models/ai-terminal-conversation.model.d.ts +17 -0
  254. package/models/ai-terminal-conversation.model.js +4 -0
  255. package/models/ai-terminal-conversation.model.js.map +1 -0
  256. package/models/ai-terminal-issue-report.model.d.ts +19 -0
  257. package/models/ai-terminal-issue-report.model.js +4 -0
  258. package/models/ai-terminal-issue-report.model.js.map +1 -0
  259. package/models/ai-terminal-message.model.d.ts +22 -0
  260. package/models/ai-terminal-message.model.js +4 -0
  261. package/models/ai-terminal-message.model.js.map +1 -0
  262. package/models/app-setting.model.d.ts +16 -0
  263. package/models/app-setting.model.js +4 -0
  264. package/models/app-setting.model.js.map +1 -0
  265. package/{src/models/app-status.model.ts → models/app-status.model.d.ts} +2 -3
  266. package/models/app-status.model.js +4 -0
  267. package/models/app-status.model.js.map +1 -0
  268. package/{src/models/billing-logged-in-users.model.ts → models/billing-logged-in-users.model.d.ts} +4 -5
  269. package/models/billing-logged-in-users.model.js +4 -0
  270. package/models/billing-logged-in-users.model.js.map +1 -0
  271. package/models/collection-document.model.d.ts +21 -0
  272. package/models/collection-document.model.js +4 -0
  273. package/models/collection-document.model.js.map +1 -0
  274. package/models/communication-metric.model.d.ts +20 -0
  275. package/models/communication-metric.model.js +4 -0
  276. package/models/communication-metric.model.js.map +1 -0
  277. package/{src/models/counter.model.ts → models/counter.model.d.ts} +3 -4
  278. package/models/counter.model.js +4 -0
  279. package/models/counter.model.js.map +1 -0
  280. package/models/cron-job-history.model.d.ts +15 -0
  281. package/models/cron-job-history.model.js +4 -0
  282. package/models/cron-job-history.model.js.map +1 -0
  283. package/models/cron-job.model.d.ts +14 -0
  284. package/models/cron-job.model.js +4 -0
  285. package/models/cron-job.model.js.map +1 -0
  286. package/models/customer-notification.model.d.ts +26 -0
  287. package/models/customer-notification.model.js +4 -0
  288. package/models/customer-notification.model.js.map +1 -0
  289. package/models/customer-portal-password.model.d.ts +11 -0
  290. package/models/customer-portal-password.model.js +4 -0
  291. package/models/customer-portal-password.model.js.map +1 -0
  292. package/models/dialog.model.d.ts +23 -0
  293. package/models/dialog.model.js +4 -0
  294. package/models/dialog.model.js.map +1 -0
  295. package/models/email-history.model.d.ts +32 -0
  296. package/{src/models/email-history.model.ts → models/email-history.model.js} +4 -36
  297. package/models/email-history.model.js.map +1 -0
  298. package/{src/models/email-verified.model.ts → models/email-verified.model.d.ts} +5 -6
  299. package/models/email-verified.model.js +4 -0
  300. package/models/email-verified.model.js.map +1 -0
  301. package/{src/models/file.model.ts → models/file.model.d.ts} +7 -8
  302. package/models/file.model.js +4 -0
  303. package/models/file.model.js.map +1 -0
  304. package/{src/models/flag-update.model.ts → models/flag-update.model.d.ts} +3 -4
  305. package/models/flag-update.model.js +4 -0
  306. package/models/flag-update.model.js.map +1 -0
  307. package/{src/models/flag.model.ts → models/flag.model.d.ts} +3 -4
  308. package/models/flag.model.js +4 -0
  309. package/models/flag.model.js.map +1 -0
  310. package/models/log-method-latency.model.d.ts +10 -0
  311. package/models/log-method-latency.model.js +4 -0
  312. package/models/log-method-latency.model.js.map +1 -0
  313. package/{src/models/log-subscription.model.ts → models/log-subscription.model.d.ts} +9 -11
  314. package/models/log-subscription.model.js +4 -0
  315. package/models/log-subscription.model.js.map +1 -0
  316. package/models/log.model.d.ts +17 -0
  317. package/models/log.model.js +4 -0
  318. package/models/log.model.js.map +1 -0
  319. package/{src/models/logged-in-users.model.ts → models/logged-in-users.model.d.ts} +5 -6
  320. package/models/logged-in-users.model.js +4 -0
  321. package/models/logged-in-users.model.js.map +1 -0
  322. package/{src/models/method-response.model.ts → models/method-response.model.d.ts} +6 -7
  323. package/models/method-response.model.js +4 -0
  324. package/models/method-response.model.js.map +1 -0
  325. package/models/method.model.d.ts +26 -0
  326. package/models/method.model.js +4 -0
  327. package/models/method.model.js.map +1 -0
  328. package/{src/models/monitor-cpu.model.ts → models/monitor-cpu.model.d.ts} +7 -9
  329. package/models/monitor-cpu.model.js +4 -0
  330. package/models/monitor-cpu.model.js.map +1 -0
  331. package/models/monitor-function.model.d.ts +14 -0
  332. package/models/monitor-function.model.js +4 -0
  333. package/models/monitor-function.model.js.map +1 -0
  334. package/models/monitor-memory.model.d.ts +15 -0
  335. package/models/monitor-memory.model.js +4 -0
  336. package/models/monitor-memory.model.js.map +1 -0
  337. package/models/monitor-mongo.model.d.ts +13 -0
  338. package/models/monitor-mongo.model.js +4 -0
  339. package/models/monitor-mongo.model.js.map +1 -0
  340. package/{src/models/notification.model.ts → models/notification.model.d.ts} +4 -6
  341. package/models/notification.model.js +4 -0
  342. package/models/notification.model.js.map +1 -0
  343. package/models/openai-usage-ledger.model.d.ts +30 -0
  344. package/models/openai-usage-ledger.model.js +4 -0
  345. package/models/openai-usage-ledger.model.js.map +1 -0
  346. package/models/pagination.model.d.ts +11 -0
  347. package/models/pagination.model.js +28 -0
  348. package/models/pagination.model.js.map +1 -0
  349. package/models/permission.model.d.ts +12 -0
  350. package/models/permission.model.js +4 -0
  351. package/models/permission.model.js.map +1 -0
  352. package/models/report-builder-dashboard-builder.model.d.ts +25 -0
  353. package/models/report-builder-dashboard-builder.model.js +4 -0
  354. package/models/report-builder-dashboard-builder.model.js.map +1 -0
  355. package/models/report-builder-library.model.d.ts +17 -0
  356. package/models/report-builder-library.model.js +4 -0
  357. package/models/report-builder-library.model.js.map +1 -0
  358. package/models/report-builder-report.model.d.ts +121 -0
  359. package/models/report-builder-report.model.js +4 -0
  360. package/models/report-builder-report.model.js.map +1 -0
  361. package/models/report-builder.model.d.ts +61 -0
  362. package/models/report-builder.model.js +4 -0
  363. package/models/report-builder.model.js.map +1 -0
  364. package/models/select-data-label.model.d.ts +9 -0
  365. package/models/select-data-label.model.js +4 -0
  366. package/models/select-data-label.model.js.map +1 -0
  367. package/models/server-message.model.d.ts +32 -0
  368. package/models/server-message.model.js +4 -0
  369. package/models/server-message.model.js.map +1 -0
  370. package/models/slow-query-report.model.d.ts +23 -0
  371. package/models/slow-query-report.model.js +4 -0
  372. package/models/slow-query-report.model.js.map +1 -0
  373. package/models/subscription.model.d.ts +31 -0
  374. package/models/subscription.model.js +4 -0
  375. package/models/subscription.model.js.map +1 -0
  376. package/models/support-ticket.model.d.ts +87 -0
  377. package/models/support-ticket.model.js +4 -0
  378. package/models/support-ticket.model.js.map +1 -0
  379. package/models/user-group.model.d.ts +20 -0
  380. package/models/user-group.model.js +4 -0
  381. package/models/user-group.model.js.map +1 -0
  382. package/{src/models/user-guide.model.ts → models/user-guide.model.d.ts} +4 -5
  383. package/models/user-guide.model.js +4 -0
  384. package/models/user-guide.model.js.map +1 -0
  385. package/models/user.model.d.ts +84 -0
  386. package/models/user.model.js +4 -0
  387. package/models/user.model.js.map +1 -0
  388. package/package.json +1 -1
  389. package/private/images/ResolveIO.png +0 -0
  390. package/public_api.js +127 -0
  391. package/public_api.js.map +1 -0
  392. package/publications/ai-terminal.d.ts +1 -0
  393. package/publications/ai-terminal.js +122 -0
  394. package/publications/ai-terminal.js.map +1 -0
  395. package/publications/app-settings.d.ts +2 -0
  396. package/publications/app-settings.js +28 -0
  397. package/publications/app-settings.js.map +1 -0
  398. package/publications/app-status.d.ts +2 -0
  399. package/publications/app-status.js +16 -0
  400. package/publications/app-status.js.map +1 -0
  401. package/publications/cron-jobs.d.ts +2 -0
  402. package/publications/cron-jobs.js +88 -0
  403. package/publications/cron-jobs.js.map +1 -0
  404. package/publications/customer-notifications.d.ts +2 -0
  405. package/publications/customer-notifications.js +161 -0
  406. package/publications/customer-notifications.js.map +1 -0
  407. package/publications/files.d.ts +2 -0
  408. package/publications/files.js +36 -0
  409. package/publications/files.js.map +1 -0
  410. package/publications/flags-update.d.ts +2 -0
  411. package/publications/flags-update.js +22 -0
  412. package/publications/flags-update.js.map +1 -0
  413. package/publications/flags.d.ts +2 -0
  414. package/publications/flags.js +22 -0
  415. package/publications/flags.js.map +1 -0
  416. package/publications/logs.d.ts +2 -0
  417. package/publications/logs.js +164 -0
  418. package/publications/logs.js.map +1 -0
  419. package/publications/notifications.d.ts +2 -0
  420. package/publications/notifications.js +16 -0
  421. package/publications/notifications.js.map +1 -0
  422. package/publications/report-builder-dashboard-builders.d.ts +2 -0
  423. package/publications/report-builder-dashboard-builders.js +42 -0
  424. package/publications/report-builder-dashboard-builders.js.map +1 -0
  425. package/publications/report-builder-libraries.d.ts +2 -0
  426. package/publications/report-builder-libraries.js +90 -0
  427. package/publications/report-builder-libraries.js.map +1 -0
  428. package/publications/report-builder-reports.d.ts +2 -0
  429. package/publications/report-builder-reports.js +50 -0
  430. package/publications/report-builder-reports.js.map +1 -0
  431. package/publications/super-admin.d.ts +2 -0
  432. package/publications/super-admin.js +16 -0
  433. package/publications/super-admin.js.map +1 -0
  434. package/publications/user-groups.d.ts +1 -0
  435. package/publications/user-groups.js +16 -0
  436. package/publications/user-groups.js.map +1 -0
  437. package/publications/user-guides.d.ts +1 -0
  438. package/publications/user-guides.js +16 -0
  439. package/publications/user-guides.js.map +1 -0
  440. package/resolveio-server-app.d.ts +70 -0
  441. package/resolveio-server-app.js +801 -0
  442. package/resolveio-server-app.js.map +1 -0
  443. package/server-app.d.ts +228 -0
  444. package/server-app.js +3566 -0
  445. package/server-app.js.map +1 -0
  446. package/services/codex-client.d.ts +128 -0
  447. package/services/codex-client.js +1629 -0
  448. package/services/codex-client.js.map +1 -0
  449. package/services/openai-client.d.ts +46 -0
  450. package/services/openai-client.js +318 -0
  451. package/services/openai-client.js.map +1 -0
  452. package/types/error-report.d.ts +25 -0
  453. package/types/error-report.js +4 -0
  454. package/types/error-report.js.map +1 -0
  455. package/types/slow-query-report.d.ts +27 -0
  456. package/types/slow-query-report.js +6 -0
  457. package/types/slow-query-report.js.map +1 -0
  458. package/util/ai-qa-policy.d.ts +124 -0
  459. package/util/ai-qa-policy.js +736 -0
  460. package/util/ai-qa-policy.js.map +1 -0
  461. package/util/ai-run-evidence-adapters.d.ts +109 -0
  462. package/util/ai-run-evidence-adapters.js +7234 -0
  463. package/util/ai-run-evidence-adapters.js.map +1 -0
  464. package/util/ai-run-evidence-dashboard.d.ts +88 -0
  465. package/util/ai-run-evidence-dashboard.js +343 -0
  466. package/util/ai-run-evidence-dashboard.js.map +1 -0
  467. package/util/ai-run-evidence-eval.d.ts +86 -0
  468. package/util/ai-run-evidence-eval.js +1018 -0
  469. package/util/ai-run-evidence-eval.js.map +1 -0
  470. package/util/ai-run-evidence.d.ts +244 -0
  471. package/util/ai-run-evidence.js +1096 -0
  472. package/util/ai-run-evidence.js.map +1 -0
  473. package/util/ai-runner-artifacts.d.ts +82 -0
  474. package/util/ai-runner-artifacts.js +713 -0
  475. package/util/ai-runner-artifacts.js.map +1 -0
  476. package/util/ai-runner-manager-autopilot.d.ts +210 -0
  477. package/util/ai-runner-manager-autopilot.js +642 -0
  478. package/util/ai-runner-manager-autopilot.js.map +1 -0
  479. package/util/ai-runner-manager-policy.d.ts +807 -0
  480. package/util/ai-runner-manager-policy.js +3501 -0
  481. package/util/ai-runner-manager-policy.js.map +1 -0
  482. package/util/ai-runner-qa-auth.d.ts +5 -0
  483. package/util/ai-runner-qa-auth.js +839 -0
  484. package/util/ai-runner-qa-auth.js.map +1 -0
  485. package/util/ai-runner-qa-tools.d.ts +26 -0
  486. package/util/ai-runner-qa-tools.js +3520 -0
  487. package/util/ai-runner-qa-tools.js.map +1 -0
  488. package/util/aicoder-runner-v6.d.ts +426 -0
  489. package/util/aicoder-runner-v6.js +2464 -0
  490. package/util/aicoder-runner-v6.js.map +1 -0
  491. package/util/common.d.ts +31 -0
  492. package/util/common.js +683 -0
  493. package/util/common.js.map +1 -0
  494. package/util/customer-portal-password.d.ts +13 -0
  495. package/util/customer-portal-password.js +209 -0
  496. package/util/customer-portal-password.js.map +1 -0
  497. package/util/error-reporter.d.ts +52 -0
  498. package/util/error-reporter.js +326 -0
  499. package/util/error-reporter.js.map +1 -0
  500. package/util/error-tracking.d.ts +13 -0
  501. package/util/error-tracking.js +120 -0
  502. package/util/error-tracking.js.map +1 -0
  503. package/util/openai-usage-cost.d.ts +6 -0
  504. package/util/openai-usage-cost.js +103 -0
  505. package/util/openai-usage-cost.js.map +1 -0
  506. package/util/report-builder-unwinds.d.ts +15 -0
  507. package/util/report-builder-unwinds.js +156 -0
  508. package/util/report-builder-unwinds.js.map +1 -0
  509. package/util/runner-process-janitor.d.ts +27 -0
  510. package/util/runner-process-janitor.js +208 -0
  511. package/util/runner-process-janitor.js.map +1 -0
  512. package/util/schema-report-builder.d.ts +6 -0
  513. package/util/schema-report-builder.js +481 -0
  514. package/util/schema-report-builder.js.map +1 -0
  515. package/util/slow-query-reporter.d.ts +28 -0
  516. package/util/slow-query-reporter.js +226 -0
  517. package/util/slow-query-reporter.js.map +1 -0
  518. package/util/subscription-dependency-context.d.ts +34 -0
  519. package/util/subscription-dependency-context.js +1283 -0
  520. package/util/subscription-dependency-context.js.map +1 -0
  521. package/util/support-runner-v5.d.ts +1426 -0
  522. package/util/support-runner-v5.js +7643 -0
  523. package/util/support-runner-v5.js.map +1 -0
  524. package/util/tokenizer.d.ts +5 -0
  525. package/util/tokenizer.js +41 -0
  526. package/util/tokenizer.js.map +1 -0
  527. package/workers/codex-runner.worker.d.ts +1 -0
  528. package/workers/codex-runner.worker.js +192 -0
  529. package/workers/codex-runner.worker.js.map +1 -0
  530. package/.nodemon.json +0 -5
  531. package/.vscode/settings.json +0 -21
  532. package/AGENTS.md +0 -195
  533. package/README.md +0 -22
  534. package/build_package.sh +0 -5
  535. package/compileDTS.pl +0 -64
  536. package/docs/ai-assistant-nightly-eval.md +0 -65
  537. package/docs/ai-assistant-preflight-checklist.md +0 -23
  538. package/docs/ai-assistant-report-builder-bridge-playbook.md +0 -115
  539. package/eslint-plugin-custom/index.js +0 -7
  540. package/eslint-plugin-custom/rules/no-filter-zero-index.js +0 -44
  541. package/eslint.config.js +0 -103
  542. package/gulpfile.js +0 -216
  543. package/methodAndPublicationListGenerator.py +0 -375
  544. package/mongodbensurers.js +0 -2
  545. package/mongostop.js +0 -3
  546. package/scripts/cleanup-bypassed-callmethod-logs.js +0 -616
  547. package/settings.development.json +0 -25
  548. package/settings.development.redacted.json +0 -25
  549. package/src/.env +0 -12
  550. package/src/ai/assistant-core-heuristics.ts +0 -379
  551. package/src/ai/resolveio-platform-intelligence-memory-corpus.ts +0 -185
  552. package/src/ai/resolveio-platform-intelligence-memory.ts +0 -325
  553. package/src/ai/resolveio-platform-intelligence.ts +0 -462
  554. package/src/client-server-app.ts +0 -12
  555. package/src/collections/ai-run.collection.ts +0 -117
  556. package/src/collections/ai-terminal-conversation.collection.ts +0 -91
  557. package/src/collections/ai-terminal-issue-report.collection.ts +0 -99
  558. package/src/collections/ai-terminal-message.collection.ts +0 -77
  559. package/src/collections/app-setting.collection.ts +0 -104
  560. package/src/collections/app-status.collection.ts +0 -58
  561. package/src/collections/communication-metric.collection.ts +0 -84
  562. package/src/collections/counter.collection.ts +0 -56
  563. package/src/collections/cron-job-history.collection.ts +0 -94
  564. package/src/collections/cron-job.collection.ts +0 -92
  565. package/src/collections/customer-notification.collection.ts +0 -131
  566. package/src/collections/customer-portal-password.collection.ts +0 -76
  567. package/src/collections/email-history.collection.ts +0 -134
  568. package/src/collections/email-verified.collection.ts +0 -62
  569. package/src/collections/file.collection.ts +0 -74
  570. package/src/collections/flag-update.collection.ts +0 -57
  571. package/src/collections/flag.collection.ts +0 -57
  572. package/src/collections/log-method-latency.collection.ts +0 -77
  573. package/src/collections/log-subscription.collection.ts +0 -80
  574. package/src/collections/log.collection.ts +0 -93
  575. package/src/collections/logged-in-users.collection.ts +0 -67
  576. package/src/collections/monitor-cpu.collection.ts +0 -65
  577. package/src/collections/monitor-function.collection.ts +0 -74
  578. package/src/collections/monitor-memory.collection.ts +0 -77
  579. package/src/collections/monitor-mongo.collection.ts +0 -71
  580. package/src/collections/notification.collection.ts +0 -57
  581. package/src/collections/openai-usage-ledger.collection.ts +0 -131
  582. package/src/collections/report-builder-dashboard-builder.collection.ts +0 -109
  583. package/src/collections/report-builder-library.collection.ts +0 -89
  584. package/src/collections/report-builder-report.collection.ts +0 -184
  585. package/src/collections/user-group.collection.ts +0 -89
  586. package/src/collections/user-guide.collection.ts +0 -57
  587. package/src/collections/user.collection.ts +0 -181
  588. package/src/cron/cron.ts +0 -117
  589. package/src/fixtures/cron-jobs.ts +0 -95
  590. package/src/fixtures/init.ts +0 -35
  591. package/src/http/auth.ts +0 -818
  592. package/src/http/health.ts +0 -7
  593. package/src/http/home.ts +0 -90
  594. package/src/http/slow-query-publication.ts +0 -49
  595. package/src/index.ts +0 -1
  596. package/src/managers/ai-assistant-codex-manager.manager.ts +0 -1131
  597. package/src/managers/ai-run-evidence.manager.ts +0 -264
  598. package/src/managers/communication-metric.manager.ts +0 -82
  599. package/src/managers/cron.manager.ts +0 -333
  600. package/src/managers/customer-notification-content.manager.ts +0 -236
  601. package/src/managers/diagnostic-manager-bootstrap.ts +0 -165
  602. package/src/managers/error-auto-fix.manager.ts +0 -2767
  603. package/src/managers/local-log.manager.ts +0 -113
  604. package/src/managers/method.manager.ts +0 -1857
  605. package/src/managers/mongo.manager.ts +0 -4575
  606. package/src/managers/monitor.manager.ts +0 -507
  607. package/src/managers/openai-usage-ledger.manager.ts +0 -112
  608. package/src/managers/slow-query-verifier.manager.ts +0 -3590
  609. package/src/managers/slow-query.manager.ts +0 -519
  610. package/src/managers/subscription.manager.ts +0 -3128
  611. package/src/managers/websocket.manager.ts +0 -746
  612. package/src/managers/worker-dispatcher.manager.ts +0 -1360
  613. package/src/managers/worker-server.manager.ts +0 -536
  614. package/src/methods/accounts.ts +0 -532
  615. package/src/methods/ai-terminal.ts +0 -29070
  616. package/src/methods/app-settings.ts +0 -114
  617. package/src/methods/aws.ts +0 -649
  618. package/src/methods/collections.ts +0 -641
  619. package/src/methods/counters.ts +0 -69
  620. package/src/methods/cron-jobs.ts +0 -2614
  621. package/src/methods/customer-notifications.ts +0 -458
  622. package/src/methods/diagnostics.ts +0 -616
  623. package/src/methods/flag-updates.ts +0 -7
  624. package/src/methods/flags.ts +0 -7
  625. package/src/methods/logs.ts +0 -657
  626. package/src/methods/mongo-explorer.ts +0 -1880
  627. package/src/methods/monitor.ts +0 -540
  628. package/src/methods/pdf.ts +0 -1236
  629. package/src/methods/publications.ts +0 -129
  630. package/src/methods/report-builder.ts +0 -3300
  631. package/src/methods/support.ts +0 -335
  632. package/src/models/ai-run.model.ts +0 -27
  633. package/src/models/ai-terminal-conversation.model.ts +0 -19
  634. package/src/models/ai-terminal-issue-report.model.ts +0 -21
  635. package/src/models/ai-terminal-message.model.ts +0 -24
  636. package/src/models/app-setting.model.ts +0 -17
  637. package/src/models/collection-document.model.ts +0 -24
  638. package/src/models/communication-metric.model.ts +0 -23
  639. package/src/models/cron-job-history.model.ts +0 -16
  640. package/src/models/cron-job.model.ts +0 -15
  641. package/src/models/customer-notification.model.ts +0 -28
  642. package/src/models/customer-portal-password.model.ts +0 -12
  643. package/src/models/dialog.model.ts +0 -25
  644. package/src/models/log-method-latency.model.ts +0 -11
  645. package/src/models/log.model.ts +0 -19
  646. package/src/models/method.model.ts +0 -25
  647. package/src/models/monitor-function.model.ts +0 -16
  648. package/src/models/monitor-memory.model.ts +0 -17
  649. package/src/models/monitor-mongo.model.ts +0 -15
  650. package/src/models/openai-usage-ledger.model.ts +0 -56
  651. package/src/models/pagination.model.ts +0 -35
  652. package/src/models/permission.model.ts +0 -14
  653. package/src/models/report-builder-dashboard-builder.model.ts +0 -29
  654. package/src/models/report-builder-library.model.ts +0 -20
  655. package/src/models/report-builder-report.model.ts +0 -136
  656. package/src/models/report-builder.model.ts +0 -68
  657. package/src/models/select-data-label.model.ts +0 -9
  658. package/src/models/server-message.model.ts +0 -31
  659. package/src/models/slow-query-report.model.ts +0 -23
  660. package/src/models/subscription.model.ts +0 -73
  661. package/src/models/support-ticket.model.ts +0 -104
  662. package/src/models/user-group.model.ts +0 -24
  663. package/src/models/user.model.ts +0 -96
  664. package/src/private/images/ResolveIO.png +0 -0
  665. package/src/publications/ai-terminal.ts +0 -73
  666. package/src/publications/app-settings.ts +0 -25
  667. package/src/publications/app-status.ts +0 -13
  668. package/src/publications/cron-jobs.ts +0 -40
  669. package/src/publications/customer-notifications.ts +0 -101
  670. package/src/publications/files.ts +0 -33
  671. package/src/publications/flags-update.ts +0 -19
  672. package/src/publications/flags.ts +0 -19
  673. package/src/publications/logs.ts +0 -163
  674. package/src/publications/notifications.ts +0 -13
  675. package/src/publications/report-builder-dashboard-builders.ts +0 -39
  676. package/src/publications/report-builder-libraries.ts +0 -41
  677. package/src/publications/report-builder-reports.ts +0 -47
  678. package/src/publications/super-admin.ts +0 -13
  679. package/src/publications/user-groups.ts +0 -12
  680. package/src/publications/user-guides.ts +0 -12
  681. package/src/resolveio-server-app.ts +0 -617
  682. package/src/server-app.ts +0 -3354
  683. package/src/services/codex-client.ts +0 -1231
  684. package/src/services/openai-client.ts +0 -265
  685. package/src/types/error-report.ts +0 -26
  686. package/src/types/js-tiktoken.d.ts +0 -11
  687. package/src/types/slow-query-report.ts +0 -28
  688. package/src/util/ai-qa-policy.ts +0 -925
  689. package/src/util/ai-run-evidence-adapters.ts +0 -8347
  690. package/src/util/ai-run-evidence-dashboard.ts +0 -323
  691. package/src/util/ai-run-evidence-eval.ts +0 -1057
  692. package/src/util/ai-run-evidence.ts +0 -1430
  693. package/src/util/ai-runner-artifacts.ts +0 -586
  694. package/src/util/ai-runner-manager-autopilot.ts +0 -961
  695. package/src/util/ai-runner-manager-policy.ts +0 -5011
  696. package/src/util/ai-runner-qa-auth.ts +0 -838
  697. package/src/util/ai-runner-qa-tools.ts +0 -3536
  698. package/src/util/aicoder-runner-v6.ts +0 -3121
  699. package/src/util/common.ts +0 -649
  700. package/src/util/customer-portal-password.ts +0 -183
  701. package/src/util/error-reporter.ts +0 -332
  702. package/src/util/error-tracking.ts +0 -79
  703. package/src/util/openai-usage-cost.ts +0 -114
  704. package/src/util/report-builder-unwinds.ts +0 -180
  705. package/src/util/runner-process-janitor.ts +0 -219
  706. package/src/util/schema-report-builder.ts +0 -448
  707. package/src/util/slow-query-reporter.ts +0 -216
  708. package/src/util/subscription-dependency-context.ts +0 -1096
  709. package/src/util/support-runner-v5.ts +0 -10040
  710. package/src/util/tokenizer.ts +0 -38
  711. package/src/workers/codex-runner.worker.ts +0 -142
  712. package/start_server.sh +0 -5
  713. package/tests/ai-assistant-corpus-build.ts +0 -484
  714. package/tests/ai-assistant-corpus-replay-e2e.ts +0 -774
  715. package/tests/ai-assistant-data-parity-e2e.ts +0 -1989
  716. package/tests/ai-assistant-eval-triage.ts +0 -831
  717. package/tests/ai-assistant-openai-e2e.ts +0 -1061
  718. package/tests/ai-assistant-openai-git-e2e.ts +0 -155
  719. package/tests/ai-assistant-preflight-matrix.ts +0 -215
  720. package/tests/ai-assistant-routing-eval.test.ts +0 -585
  721. package/tests/ai-assistant-snf-live-eval.ts +0 -975
  722. package/tests/ai-assistant-utils.test.ts +0 -4834
  723. package/tests/ai-manager-autopilot-snapshot.test.ts +0 -193
  724. package/tests/ai-manager-recovery-checkpoint.test.ts +0 -1383
  725. package/tests/ai-run-eval.test.ts +0 -132
  726. package/tests/ai-run-evidence.test.ts +0 -3773
  727. package/tests/ai-runner-contract.test.ts +0 -515
  728. package/tests/aicoder-runner-v6.test.ts +0 -822
  729. package/tests/error-reporter.test.ts +0 -145
  730. package/tests/method-publication-generator.test.ts +0 -46
  731. package/tests/report-builder-linking.test.ts +0 -79
  732. package/tests/resolveio-platform-intelligence.test.ts +0 -352
  733. package/tests/server-app-cron-owner.test.ts +0 -127
  734. package/tests/subscription-connect-race.test.ts +0 -158
  735. package/tests/subscription-dependency-context.test.ts +0 -324
  736. package/tests/subscription-manager-collection-tracking.test.ts +0 -86
  737. package/tests/subscription-manager-invalidation.test.ts +0 -86
  738. package/tests/support-runner-v5.test.ts +0 -3201
  739. package/tsconfig.json +0 -34
  740. /package/{src/private → private}/email-templates/enrollment.html +0 -0
  741. /package/{src/private → private}/email-templates/forgot-password.html +0 -0
  742. /package/{src/private → private}/email-templates/support-ticket-deleted.html +0 -0
  743. /package/{src/private → private}/email-templates/support-ticket-modified.html +0 -0
  744. /package/{src/private → private}/email-templates/support-ticket.html +0 -0
  745. /package/{src/public_api.ts → public_api.d.ts} +0 -0
@@ -1,3300 +0,0 @@
1
- import SimpleSchema from 'simpl-schema';
2
- import { ReportBuilderReports } from '../collections/report-builder-report.collection';
3
- import { Users } from '../collections/user.collection';
4
- import { MethodManager } from '../managers/method.manager';
5
- import { recordOpenAIUsage } from '../managers/openai-usage-ledger.manager';
6
- import { PaginationOptions, PaginationOptionsSchema } from '../models/pagination.model';
7
- import { ReportBuilderCollectionJoin } from '../models/report-builder-report.model';
8
- import { ResolveIOServer } from '../resolveio-server-app';
9
- import { CodexClient, CodexConfig, CodexRunOptions, CodexThreadOptions } from '../services/codex-client';
10
- import { deepCopy, round, toTitleCase } from '../util/common';
11
- import { applyLinkFieldUnwinds, ensureLookupLocalKeyUnwinds, normalizeLookupField } from '../util/report-builder-unwinds';
12
- import { buildTree, getReportLookupSchemas, mongoCommand } from '../util/schema-report-builder';
13
- import { countChatTokens, countTokens } from '../util/tokenizer';
14
- import { executeAiAssistantMongoRead, AiAssistantMongoReadInput } from './ai-terminal';
15
-
16
- const DEFAULT_REPORT_BUILDER_CODEX_TIMEOUT_MS = 60000;
17
-
18
- let reportBuilderCodexClient: CodexClient | null = null;
19
-
20
- export function loadReportBuilderMethods(methodManager: MethodManager) {
21
- methodManager.methods({
22
- reportBuilderAiSuggest: {
23
- check: new SimpleSchema({
24
- payload: {
25
- type: Object,
26
- blackbox: true
27
- }
28
- }),
29
- function: async function(payload: any) {
30
- return await executeReportBuilderAi(payload || {}, this);
31
- }
32
- },
33
- reportbuilderreportWithId: {
34
- check: new SimpleSchema({
35
- id: {
36
- type: String
37
- }
38
- }),
39
- function: function(id: string) {
40
- return ReportBuilderReports.findOne({_id: id});
41
- }
42
- },
43
- reportBuilderBuildTree: {
44
- check: new SimpleSchema({
45
- collection_root: {
46
- type: String
47
- },
48
- collectionJoins: {
49
- type: Array,
50
- optional: true
51
- },
52
- 'collectionJoins.$': {
53
- type: Object
54
- },
55
- 'collectionJoins.$.collection': {
56
- type: String
57
- },
58
- 'collectionJoins.$.alias': {
59
- type: String,
60
- optional: true
61
- },
62
- 'collectionJoins.$.local_key': {
63
- type: String,
64
- optional: true
65
- },
66
- 'collectionJoins.$.foreign_key': {
67
- type: String,
68
- optional: true
69
- }
70
- }),
71
- function: function(collection_root: string, collectionJoins: any[] = []) {
72
- let baseRootTree = buildCollectionTree(collection_root);
73
-
74
- let collectionTrees: any[] = [
75
- {
76
- collection: collection_root,
77
- alias: toTitleCase(collection_root.replace(/\_/g, ' ').replace(/-/g, ' ')),
78
- lookup_as: '',
79
- tree: deepCopy(baseRootTree)
80
- }
81
- ];
82
-
83
- let rootTree = baseRootTree;
84
-
85
- (collectionJoins || []).forEach((join, joinIndex) => {
86
- const joinLocalKey = join.local_key && join.local_key.trim().length ? join.local_key : '_id';
87
- const joinForeignKey = join.foreign_key && join.foreign_key.trim().length ? join.foreign_key : '_id';
88
-
89
- let joinAlias = buildJoinAlias(join, joinIndex);
90
- let joinNode = buildJoinLookupNode(collection_root, {...join, local_key: joinLocalKey, foreign_key: joinForeignKey}, joinAlias);
91
-
92
- let joinTree = buildCollectionTree(join.collection, joinNode);
93
- joinNode.children = joinTree.children;
94
- joinNode.is_join = true;
95
-
96
- rootTree.children.push(joinNode);
97
-
98
- collectionTrees.push({
99
- collection: join.collection,
100
- alias: joinAlias,
101
- lookup_as: joinNode.lookup_as,
102
- tree: deepCopy(joinTree)
103
- });
104
- });
105
-
106
- return Promise.resolve({
107
- isLeaf: false,
108
- children: rootTree.children,
109
- trees: collectionTrees
110
- });
111
- }
112
- },
113
- reportBuilderGetDistinctValue: {
114
- check: new SimpleSchema({
115
- treeLeaf: {
116
- type: Object,
117
- blackbox: true
118
- }
119
- }),
120
- function: async function(treeLeaf) {
121
- if (treeLeaf.fieldType === 'String') {
122
- const fieldPath = (treeLeaf && typeof treeLeaf.fieldPath === 'string') ? treeLeaf.fieldPath : '';
123
- if (fieldPath.includes('(Lookup') || treeLeaf.lookup_collection) {
124
- return mongoCommand('distinct', treeLeaf.collection_name, fieldPath.replace(/^.+\(.+\) \(Lookup.*\)\.\$\./g, '').replace(/\.\$/g, ''));
125
- }
126
- else {
127
- return mongoCommand('distinct', treeLeaf.collection_name, fieldPath.replace(/\.\$/g, ''));
128
- }
129
- }
130
- else {
131
- return [];
132
- }
133
- }
134
- },
135
- reportBuilderGetResults: {
136
- bypassSession: true,
137
- check: new SimpleSchema({
138
- reportType: {
139
- type: String
140
- },
141
- rootCollectionName: {
142
- type: String
143
- },
144
- options: {
145
- type: PaginationOptionsSchema
146
- },
147
- filters: {
148
- type: Array,
149
- optional: true
150
- },
151
- 'filters.$': {
152
- type: Object,
153
- blackbox: true,
154
- optional: true
155
- },
156
- filterArrays: {
157
- type: Array,
158
- optional: true
159
- },
160
- 'filterArrays.$': {
161
- type: Object,
162
- blackbox: true,
163
- optional: true
164
- },
165
- filterArrayFields: {
166
- type: Array,
167
- optional: true
168
- },
169
- 'filterArrayFields.$': {
170
- type: Object,
171
- blackbox: true,
172
- optional: true
173
- },
174
- selectedFields: {
175
- type: Array,
176
- optional: true
177
- },
178
- 'selectedFields.$': {
179
- type: Object,
180
- optional: true,
181
- blackbox: true
182
- },
183
- customFields: {
184
- type: Array,
185
- optional: true
186
- },
187
- 'customFields.$': {
188
- type: Object,
189
- optional: true,
190
- blackbox: true
191
- },
192
- groupsRow: {
193
- type: Array
194
- },
195
- 'groupsRow.$': {
196
- type: Object,
197
- blackbox: true
198
- },
199
- fieldsTotal: {
200
- type: Array
201
- },
202
- 'fieldsTotal.$': {
203
- type: Object,
204
- blackbox: true
205
- },
206
- fieldsLink: {
207
- type: Array
208
- },
209
- 'fieldsLink.$': {
210
- type: Object,
211
- blackbox: true
212
- },
213
- date_field: {
214
- type: String,
215
- optional: true
216
- },
217
- date_interval: {
218
- type: String,
219
- optional: true
220
- },
221
- displayType: {
222
- type: String,
223
- optional: true
224
- }
225
- }),
226
- function: async function(reportType: string, rootCollectionName: string, rootOptions: PaginationOptions, filters: any[] = [], filterArrays: any[] = [], filterArrayFields: any[] = [], selectedFields: any[] = [], customFields: any[] = [], groupsRow: any[] = [], fieldsTotal: any[] = [], fieldsLink: any[] = [], date_field = '', date_interval = '', displayType = '') {
227
- let modelCollection = ResolveIOServer.getMongoManager().collection(rootCollectionName);
228
-
229
- if (!modelCollection) {
230
- throw new Error('Error in Report builder get results: Invalid collection');
231
- }
232
- else {
233
- filters = expandLayoutColumnFilters(filters, selectedFields);
234
- filterArrays = expandLayoutColumnFilters(filterArrays, selectedFields);
235
- const datePaths = collectRootDatePaths(rootCollectionName);
236
- if (datePaths.size) {
237
- coerceDateFilters(filters, datePaths);
238
- coerceDateFilters(filterArrays, datePaths);
239
- }
240
- const reportBuilderTimezone = process.env.TZ_CLIENT || 'America/Chicago';
241
-
242
- const lookupAliases = collectLookupAliases(selectedFields, filterArrayFields, groupsRow);
243
- const {rootFilters, lookupFilters} = splitFiltersByLookup(filters, lookupAliases);
244
-
245
- // Union-based join mode (no local/foreign key): build per-collection pipelines and union them
246
- // Union-based join mode should only apply when a "joined" collection has no join keys.
247
- // Standard lookups (with local/foreign keys) must use the aggregation $lookup pipeline below,
248
- // otherwise we'd return unrelated docs from the foreign collection (and root fields like "well" go blank).
249
- const additionalCollectionsMap = new Map<string, {collection: string, alias: string}>();
250
- (selectedFields || [])
251
- .filter(f =>
252
- f &&
253
- f.lookup_collection &&
254
- (!f.lookup_local_key || !f.lookup_foreign_key)
255
- )
256
- .forEach(f => {
257
- const alias = f.lookup_as || f.lookup_collection;
258
- const key = `${f.lookup_collection}::${alias}`;
259
- if (!additionalCollectionsMap.has(key)) {
260
- additionalCollectionsMap.set(key, {collection: f.lookup_collection, alias});
261
- }
262
- });
263
- const additionalCollections = Array.from(additionalCollectionsMap.values());
264
-
265
- if (additionalCollections.length) {
266
- let unionQuery: any[] = buildCollectionPipeline(rootCollectionName, '', selectedFields, filters.concat(rootFilters || []), filterArrays.concat(lookupFilters || []), true);
267
-
268
- additionalCollections.forEach(cfg => {
269
- unionQuery.push({
270
- $unionWith: {
271
- coll: cfg.collection,
272
- pipeline: buildCollectionPipeline(cfg.collection, cfg.alias, selectedFields, filters.concat(rootFilters || []), filterArrays.concat(lookupFilters || []), false)
273
- }
274
- });
275
- });
276
-
277
- if (rootOptions.sort) {
278
- const {sortSpec: sortToUse, addFields: layoutSortAdd} = buildLayoutSortSpec(rootOptions.sort, selectedFields);
279
- if (Object.keys(layoutSortAdd).length) {
280
- unionQuery.push({$addFields: layoutSortAdd});
281
- }
282
- appendSafeSort(unionQuery, sortToUse, selectedFields, customFields);
283
- }
284
-
285
- // Apply remapped filters on projected field ids after union
286
- const remappedFilters = remapFiltersToIds(filters.concat(rootFilters || []), selectedFields);
287
- const remappedArrayFilters = remapFiltersToIds(filterArrays.concat(lookupFilters || []), selectedFields);
288
- const postMatchAnd: any[] = [];
289
- if (remappedFilters.length) {
290
- postMatchAnd.push(...remappedFilters);
291
- }
292
- if (remappedArrayFilters.length) {
293
- postMatchAnd.push(...remappedArrayFilters);
294
- }
295
- if (postMatchAnd.length) {
296
- unionQuery.push({$match: {$and: postMatchAnd}});
297
- }
298
-
299
- const readPreference = this.serverConfig['ROOT_URL'] !== 'http://localhost:4200' ? 'secondary' : 'primary';
300
- let queryResult = {
301
- count: 0,
302
- results: [],
303
- totals: {}
304
- };
305
-
306
- try {
307
- queryResult = await executeReportBuilderQuery(modelCollection, unionQuery, rootOptions, fieldsTotal, selectedFields, customFields, readPreference);
308
- }
309
- catch (err) {
310
- console.log(err);
311
- }
312
-
313
- let tmpRes = [{
314
- count: queryResult.count,
315
- results: queryResult.results,
316
- totals: queryResult.totals
317
- }];
318
-
319
- return tmpRes;
320
- }
321
-
322
- // Query
323
- let query = [];
324
-
325
- // Non-Lookup Match Filters
326
- let initialQueryMatchCondition = {
327
- $and: []
328
- };
329
-
330
- // selectedFields.filter(a => a.collection_name === rootCollectionName).forEach(field => {
331
- // initialQueryMatchCondition['$and'].push({
332
- // $and: [
333
- // {[field.fieldPath.replace(/\.\$/g, '')]: {$exists: 1}},
334
- // // {[field.fieldPath.replace(/\.\$/g, '')]: {$ne: ''}},
335
- // {[field.fieldPath.replace(/\.\$/g, '')]: {$ne: null}},
336
- // ]
337
- // });
338
- // });
339
-
340
- if (rootFilters.length) {
341
- rootFilters.forEach(filter => initialQueryMatchCondition['$and'].push(filter));
342
- }
343
-
344
- // Add filter to query
345
- // if (filters.filter(a => (!a['$or'] && !Object.keys(a).some(b => b.includes('(Lookup'))) || (a['$or'] && !a['$or'].some(b => Object.keys(b).some(c => c.includes('(Lookup)'))))).length || selectedFields.filter(a => !a.fieldPath.includes('(Lookup')).length) {
346
- if (initialQueryMatchCondition['$and'].length) {
347
- query.push({
348
- $match: initialQueryMatchCondition
349
- });
350
- }
351
-
352
- // Add count
353
- query.push({ $addFields: { count: { $sum: 1} } });
354
-
355
- let sizes = [];
356
-
357
- filterArrayFields.filter(a => a.fieldPath).forEach(filterField => {
358
- let fieldData = filterField.fieldPath.split('.$');
359
-
360
- let fieldPath = '';
361
-
362
- for (let i = 0; i < fieldData.length - 1; i++) {
363
- fieldPath += fieldData[i];
364
-
365
- if (filterField.lookup_collection || fieldPath.split('.$')[fieldPath.split('.$').length - 1].includes('(Lookup')) {
366
- ensureLookupLocalKeyUnwinds({
367
- query,
368
- sizes,
369
- lookupLocalKey: filterField.lookup_local_key || '',
370
- lookupAs: filterField.lookup_as || ''
371
- });
372
-
373
- const lookupAs = filterField.lookup_as.replace(/\.\$/g, '');
374
- if (!query.some(a => a.$lookup && a.$lookup.as === lookupAs)) {
375
- query.push({$lookup: {
376
- from: filterField.lookup_collection,
377
- localField: normalizeLookupField(filterField.lookup_local_key),
378
- foreignField: normalizeLookupField(filterField.lookup_foreign_key),
379
- as: lookupAs
380
- }});
381
-
382
- const lookupSizeKey = filterField.lookup_as.replace(/\.\$\./g, '_').replace(/\./g, '_');
383
- sizes.push(lookupSizeKey);
384
- const lookupSourceExpr = '$' + lookupAs;
385
- query.push({$addFields: {['size_' + lookupSizeKey]: {$size: {$cond: [{$isArray: lookupSourceExpr}, lookupSourceExpr, []]}}}});
386
-
387
- const unwindPath = '$' + lookupAs;
388
- query.push({'$unwind': {path: unwindPath, preserveNullAndEmptyArrays: true}});
389
- }
390
- }
391
- else {
392
- if (!query.some(a => a.$addFields && Object.keys(a.$addFields).includes('size_' + fieldPath.replace(/\./g, '_')))) {
393
- sizes.push(fieldPath.replace(/\./g, '_'));
394
- const sourceExpr = '$' + fieldPath;
395
- query.push({$addFields: {['size_' + fieldPath.replace(/\./g, '_')]: {$size: {$cond: [{$isArray: sourceExpr}, sourceExpr, []]}}}});
396
-
397
- const unwindPath = '$' + fieldPath;
398
- query.push({'$unwind': {path: unwindPath, preserveNullAndEmptyArrays: true}});
399
- }
400
- }
401
- }
402
- });
403
-
404
- // Groups - Lookups / Sizes / Unwinds
405
- groupsRow.forEach(row => {
406
- const rowField = (row && typeof row.field === 'string') ? row.field : '';
407
- if (rowField.includes('$')) {
408
- let fieldData = rowField.split('.$');
409
-
410
- let fieldPath = '';
411
-
412
- for (let i = 0; i < fieldData.length - 1; i++) {
413
- fieldPath += fieldData[i];
414
-
415
- const lastSegment = fieldPath.split('.$')[fieldPath.split('.$').length - 1];
416
- const hasLookup = !!row.treeItem?.lookup_collection || (lastSegment || '').includes('(Lookup');
417
-
418
- if (hasLookup && row.treeItem?.lookup_collection && row.treeItem?.lookup_as) {
419
- ensureLookupLocalKeyUnwinds({
420
- query,
421
- sizes,
422
- lookupLocalKey: row.treeItem.lookup_local_key || '',
423
- lookupAs: row.treeItem.lookup_as || ''
424
- });
425
-
426
- const lookupAsRaw = row.treeItem.lookup_as || '';
427
- const lookupAs = lookupAsRaw.replace(/\.\$/g, '');
428
-
429
- if (!query.some(a => a.$lookup && a.$lookup.as === lookupAs)) {
430
- query.push({$lookup: {
431
- from: row.treeItem.lookup_collection,
432
- localField: normalizeLookupField(row.treeItem.lookup_local_key || ''),
433
- foreignField: normalizeLookupField(row.treeItem.lookup_foreign_key || ''),
434
- as: lookupAs
435
- }});
436
-
437
- const sizeKey = lookupAsRaw.replace(/\.\$\./g, '_').replace(/\./g, '_');
438
- sizes.push(sizeKey);
439
- const sourceExpr = '$' + lookupAs;
440
- query.push({$addFields: {['size_' + sizeKey]: {$size: {$cond: [{$isArray: sourceExpr}, sourceExpr, []]}}}});
441
-
442
- const unwindPath = '$' + lookupAs;
443
- query.push({'$unwind': {path: unwindPath, preserveNullAndEmptyArrays: true}});
444
- }
445
- }
446
- else {
447
- if (!query.some(a => a.$addFields && Object.keys(a.$addFields).includes('size_' + fieldPath.replace(/\.\$\./g, '.').replace(/\./g, '_')))) {
448
- const sizeKey = fieldPath.replace(/\.\$\./g, '.').replace(/\./g, '_');
449
- sizes.push(sizeKey);
450
- const sourceExpr = '$' + fieldPath.replace(/\.\$\./g, '.');
451
- query.push({$addFields: {['size_' + sizeKey]: {$size: {$cond: [{$isArray: sourceExpr}, sourceExpr, []]}}}});
452
- const unwindPath = '$' + fieldPath.replace(/\.\$\./g, '.');
453
- query.push({'$unwind': {path: unwindPath, preserveNullAndEmptyArrays: true}});
454
- }
455
- }
456
-
457
- fieldPath += '.$';
458
- }
459
- }
460
- });
461
-
462
- applyLinkFieldUnwinds({
463
- query,
464
- sizes,
465
- fieldsLink,
466
- selectedFields,
467
- filterArrayFields,
468
- groupsRow
469
- });
470
-
471
- // Selected Fields - Lookups / Sizes / Unwinds
472
- // NOTE: lookup-based unwinds are no longer needed for joins; union handled below
473
-
474
- // Divide all number fields by sizes
475
- let divFields = {$addFields: {}};
476
-
477
- fieldsTotal.filter(a => a.type === 'sum').forEach(total => {
478
- selectedFields.filter(a => a.fieldType === 'Number' || a.leafValueType === 'Count').forEach(field => {
479
- const fieldPath = (field && typeof field.fieldPath === 'string') ? field.fieldPath : '';
480
- if (!fieldPath && field.leafValueType !== 'Count') {
481
- return;
482
- }
483
-
484
- let multFields = [];
485
-
486
- if (!fieldPath.includes('.$')) {
487
- sizes.forEach(size => {
488
- multFields.push({$cond: { if: { $gt: ['$size_' + size, 0 ] }, then: '$size_' + size, else: 1 }});
489
- });
490
- }
491
- else {
492
- sizes.filter(a => !fieldPath.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a) && !filterArrayFields.filter(a => a.fieldPath).some(b => b.fieldPath.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a)) && !fieldsLink.some(b => b.field_first.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a) || b.field_second.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a))).forEach(size => {
493
- multFields.push({$cond: { if: { $gt: ['$size_' + size, 0 ] }, then: '$size_' + size, else: 1 }});
494
- });
495
- }
496
-
497
- if (multFields.length) {
498
- if (field.leafValueType === 'Count') {
499
- divFields.$addFields[total.id + '_' + field.id] = {
500
- $divide: ['$count', {$multiply: multFields}]
501
- };
502
- }
503
- else {
504
- divFields.$addFields[total.id + '_' + field.id] = {
505
- $divide: [buildArrayAwareNumericFieldExpr(fieldPath), {$multiply: multFields}]
506
- };
507
- }
508
- }
509
- else {
510
- if (field.leafValueType === 'Count') {
511
- divFields.$addFields[total.id + '_' + field.id] = '$count';
512
- }
513
- else {
514
- divFields.$addFields[total.id + '_' + field.id] = buildArrayAwareNumericFieldExpr(fieldPath);
515
- }
516
- }
517
- });
518
- });
519
-
520
- selectedFields.filter(a => a.fieldType === 'Number' && a.leafValueType === 'Sum').forEach(field => {
521
- const fieldPath = (field && typeof field.fieldPath === 'string') ? field.fieldPath : '';
522
- if (!fieldPath) {
523
- return;
524
- }
525
-
526
- let multFields = [];
527
-
528
- sizes.filter(a => !fieldPath.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a) && !groupsRow.some(b => (b.field || '').replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a)) && !filterArrayFields.filter(a => a.fieldPath).some(b => b.fieldPath.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a)) && !fieldsLink.some(b => b.field_first.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a) || b.field_second.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a))).forEach(size => {
529
- multFields.push({$cond: { if: { $gt: ['$size_' + size, 0 ] }, then: '$size_' + size, else: 1 }});
530
- });
531
-
532
- if (multFields.length) {
533
- divFields.$addFields[fieldPath.replace(/\.\$/g, '')] = {
534
- $divide: [buildArrayAwareNumericFieldExpr(fieldPath), {$multiply: multFields}]
535
- };
536
- }
537
- });
538
-
539
- selectedFields.filter(a => a.leafValueType === 'Count').forEach(field => {
540
- const fieldPath = (field && typeof field.fieldPath === 'string') ? field.fieldPath : '';
541
- if (!fieldPath) {
542
- return;
543
- }
544
-
545
- let multFields = [];
546
-
547
- sizes.filter(a => !fieldPath.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a) && !groupsRow.some(b => (b.field || '').replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a)) && !filterArrayFields.filter(a => a.fieldPath).some(b => b.fieldPath.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a)) && !fieldsLink.some(b => b.field_first.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a) || b.field_second.replace(/\.\$\./g, '_').replace(/\./g, '_').includes(a))).forEach(size => {
548
- multFields.push({$cond: { if: { $gt: ['$size_' + size, 0 ] }, then: '$size_' + size, else: 1 }});
549
- });
550
-
551
- if (multFields.length) {
552
- divFields.$addFields['count_' + fieldPath.replace(/\.\$/g, '')] = {
553
- $divide: ['$count', {$multiply: multFields}]
554
- };
555
- }
556
- else {
557
- if (reportType !== 'Dated' && sizes.filter(a => fieldPath.replace(/\.\$\./g, '_').includes(a)).length) {
558
- sizes.filter(a => fieldPath.replace(/\.\$\./g, '_').includes(a)).forEach(size => {
559
- divFields.$addFields['count_' + fieldPath.replace(/\.\$/g, '')] = '$size_' + size;
560
- });
561
- }
562
- else {
563
- divFields.$addFields['count_' + fieldPath.replace(/\.\$/g, '')] = '$count';
564
- }
565
- }
566
- });
567
-
568
- fieldsTotal.filter(a => a.type === 'avg').forEach(total => {
569
- selectedFields.filter(a => a.fieldType === 'Number' || a.leafValueType === 'Count').forEach(field => {
570
- const fieldPath = (field && typeof field.fieldPath === 'string') ? field.fieldPath : '';
571
- if (!fieldPath && field.leafValueType !== 'Count') {
572
- return;
573
- }
574
- divFields.$addFields[total.id + '_' + field.id] = buildArrayAwareNumericFieldExpr(fieldPath);
575
- });
576
- });
577
-
578
- if (Object.keys(divFields.$addFields).length > 0) {
579
- query.push(divFields);
580
- }
581
-
582
- fieldsLink.filter(a => a.field_first && a.field_second).forEach(link => {
583
- query.push({$addFields: {[link.id]: {$cmp: ['$' + link.field_first.replace(/\.\$/g, ''), '$' + link.field_second.replace(/\.\$/g, '')]}}});
584
- query.push({$match: {[link.id]: 0}});
585
- });
586
-
587
- customFields.forEach(cust => {
588
- cust.operations.filter(a => a.operation && !a.fields.some(b => (b.field === null || b.field === '') && b.value === null)).forEach((operation, opIndex) => {
589
- let custFieldGroup = {};
590
-
591
- if (operation.operation === 'divide') {
592
- let condCheck = [];
593
- operation.fields.filter(a => a.field || a.value !== null).forEach(field => {
594
- if (field.field) {
595
- condCheck.push({$eq: ['$' + field.field.replace(/\.\$/g, ''), 0]});
596
- condCheck.push({$eq: ['$' + field.field.replace(/\.\$/g, ''), null]});
597
- }
598
- else {
599
- condCheck.push({$eq: [field.value, 0]});
600
- condCheck.push({$eq: [field.value, null]});
601
- }
602
- });
603
-
604
- if (opIndex > 0) {
605
- condCheck.push({$eq: ['$' + cust.selFieldId, 0]});
606
- condCheck.push({$eq: ['$' + cust.selFieldId, null]});
607
- }
608
-
609
- let tmpOperationFields = [];
610
-
611
- if (opIndex > 0) {
612
- tmpOperationFields.push('$' + cust.selFieldId);
613
- }
614
-
615
- operation.fields.filter(a => a.field || a.value !== null).forEach(field => {
616
- if (field.field) {
617
- tmpOperationFields.push('$' + field.field.replace(/\.\$/g, ''));
618
- }
619
- else {
620
- tmpOperationFields.push(field.value);
621
- }
622
- });
623
-
624
- custFieldGroup[cust.selFieldId] = {
625
- $cond: { if: { $or: condCheck }, then: 0, else: {['$' + operation.operation]: tmpOperationFields} }
626
- };
627
- }
628
- else {
629
- custFieldGroup[cust.selFieldId] = {
630
- ['$' + operation.operation]: []
631
- };
632
-
633
- if (opIndex > 0) {
634
- custFieldGroup[cust.selFieldId]['$' + operation.operation].push({$cond: { if: { $eq: ['$' + cust.selFieldId, null] }, then: 0, else: '$' + cust.selFieldId}});
635
- }
636
-
637
- let condOr = [];
638
-
639
- operation.fields.filter(a => a.field || a.value !== null).forEach(field => {
640
- if (field.field) {
641
- condOr.push({$eq: ['$' + field.field.replace(/\.\$/g, ''), null]});
642
- }
643
- else {
644
- condOr.push({$eq: [field.value, null]});
645
- }
646
- });
647
-
648
- operation.fields.filter(a => a.field || a.value !== null).forEach(field => {
649
- if (field.field) {
650
- custFieldGroup[cust.selFieldId]['$' + operation.operation].push({$cond: { if: { $or: condOr }, then: 0, else: '$' + field.field.replace(/\.\$/g, '')}});
651
- }
652
- else {
653
- custFieldGroup[cust.selFieldId]['$' + operation.operation].push({$cond: { if: { $or: condOr }, then: 0, else: field.value}});
654
- }
655
- });
656
- }
657
-
658
- query.push({$addFields: custFieldGroup});
659
- });
660
- });
661
-
662
- fieldsTotal.forEach(total => {
663
- customFields.forEach(field => {
664
- query.push({$addFields:{[total.id + '_' + field.selFieldId]: '$' + field.selFieldId}});
665
- });
666
- });
667
-
668
- // Filters - Lookups / Arrays
669
- let queryMatchConditionLookup = {
670
- $and: []
671
- };
672
-
673
- if (lookupFilters.length) {
674
- lookupFilters.forEach(filter => queryMatchConditionLookup['$and'].push(filter));
675
- }
676
-
677
- filterArrays.forEach(filter => {
678
- queryMatchConditionLookup['$and'].push(filter);
679
- });
680
-
681
- if (queryMatchConditionLookup.$and.length) {
682
- query.push({$match: queryMatchConditionLookup});
683
- }
684
-
685
- // Group
686
- let queryGroup = {
687
- _id: {}
688
- };
689
-
690
- if (groupsRow.length || !displayType || displayType === 'grouped') {
691
- if (!groupsRow.length) {
692
- queryGroup._id = '$_id';
693
- }
694
- else {
695
- if (rootOptions.sort) {
696
- let groupedSorts = {};
697
- Object.keys(rootOptions.sort).forEach(sortField => {
698
- let selField = selectedFields.find(a => a.id === sortField);
699
-
700
- if (selField) {
701
- const sortPath = (selField && typeof selField.fieldPath === 'string') ? selField.fieldPath.replace(/\.\$/g, '') : '';
702
- if (sortPath) {
703
- groupedSorts[sortPath] = rootOptions.sort[sortField];
704
- }
705
- }
706
- });
707
- if (Object.keys(groupedSorts).length > 0) {
708
- query.push({
709
- $sort: groupedSorts
710
- });
711
- }
712
- }
713
- }
714
-
715
- if (groupsRow.length && date_field && date_interval) {
716
- const dateFieldPath = (typeof date_field === 'string') ? date_field.replace(/\.\$/g, '') : '';
717
- if (dateFieldPath) {
718
- const dateFieldExpr = '$' + dateFieldPath;
719
- const utcHourExpr = {'$hour': {date: dateFieldExpr, timezone: 'UTC'}};
720
- const utcMinuteExpr = {'$minute': {date: dateFieldExpr, timezone: 'UTC'}};
721
- const utcSecondExpr = {'$second': {date: dateFieldExpr, timezone: 'UTC'}};
722
- const utcDayExpr = {'$dayOfMonth': {date: dateFieldExpr, timezone: 'UTC'}};
723
- const tzDayExpr = {'$dayOfMonth': {date: dateFieldExpr, timezone: reportBuilderTimezone}};
724
- const useUtcForDateOnlyExpr = {
725
- $and: [
726
- {$eq: [utcHourExpr, 0]},
727
- {$eq: [utcMinuteExpr, 0]},
728
- {$eq: [utcSecondExpr, 0]},
729
- {$ne: [utcDayExpr, tzDayExpr]}
730
- ]
731
- };
732
- const groupingTimezoneExpr = {$cond: [useUtcForDateOnlyExpr, 'UTC', reportBuilderTimezone]};
733
-
734
- if (date_interval === 'Seconds') {
735
- queryGroup._id['second'] = {'$second' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
736
- queryGroup._id['minute'] = {'$minute' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
737
- queryGroup._id['hour'] = {'$hour' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
738
- queryGroup._id['day'] = {'$dayOfMonth' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
739
- queryGroup._id['month'] = {'$month' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
740
- queryGroup._id['year'] = {'$year' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
741
- }
742
- else if (date_interval === 'Minutes') {
743
- queryGroup._id['minute'] = {'$minute' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
744
- queryGroup._id['hour'] = {'$hour' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
745
- queryGroup._id['day'] = {'$dayOfMonth' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
746
- queryGroup._id['month'] = {'$month' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
747
- queryGroup._id['year'] = {'$year' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
748
- }
749
- else if (date_interval === 'Hours') {
750
- queryGroup._id['hour'] = {'$hour' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
751
- queryGroup._id['day'] = {'$dayOfMonth' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
752
- queryGroup._id['month'] = {'$month' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
753
- queryGroup._id['year'] = {'$year' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
754
- }
755
- else if (date_interval === 'Daily') {
756
- queryGroup._id['day'] = {'$dayOfMonth' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
757
- queryGroup._id['month'] = {'$month' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
758
- queryGroup._id['year'] = {'$year' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
759
- }
760
- else if (date_interval === 'Weekly') {
761
- queryGroup._id['week'] = {'$isoWeek' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
762
- queryGroup._id['year'] = {'$isoWeekYear' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
763
- }
764
- else if (date_interval === 'Monthly') {
765
- queryGroup._id['month'] = {'$month' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
766
- queryGroup._id['year'] = {'$year' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
767
- }
768
- else if (date_interval === 'Quarterly') {
769
- queryGroup._id['month'] = {'$month' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
770
- queryGroup._id['year'] = {'$year' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
771
- }
772
- else if (date_interval === 'Yearly') {
773
- queryGroup._id['year'] = {'$year' : {'date': dateFieldExpr, 'timezone': groupingTimezoneExpr}};
774
- }
775
- }
776
- }
777
-
778
- groupsRow.forEach(row => {
779
- const rowField = (row && typeof row.field === 'string') ? row.field : '';
780
- if (!rowField || !row?.id) {
781
- return;
782
- }
783
-
784
- queryGroup._id[row.id] = '$' + rowField.replace(/\.\$/g, '');
785
- });
786
-
787
- selectedFields.forEach(field => {
788
- const fieldPath = (field && typeof field.fieldPath === 'string') ? field.fieldPath : '';
789
- if (!fieldPath) {
790
- if (field.leafValueType === 'Count') {
791
- queryGroup[field.id] = { '$sum': 1 };
792
- }
793
- return;
794
- }
795
-
796
- if (field.leafValueType === 'Average') {
797
- queryGroup[field.id] = {'$avg': '$' + fieldPath.replace(/\.\$/g, '')};
798
- }
799
- else if (field.leafValueType === 'Sum') {
800
- queryGroup[field.id] = {'$sum': buildArrayAwareNumericFieldExpr(fieldPath)};
801
- }
802
- else if (field.leafValueType === 'Count') {
803
- queryGroup[field.id] = { '$sum': '$count_' + fieldPath.replace(/\.\$/g, '') };
804
- }
805
- else if (field.leafValueType === 'Minimum') {
806
- queryGroup[field.id] = {'$min': '$' + fieldPath.replace(/\.\$/g, '')};
807
- }
808
- else if (field.leafValueType === 'Maximum') {
809
- queryGroup[field.id] = {'$max': '$' + fieldPath.replace(/\.\$/g, '')};
810
- }
811
- else if (field.leafValueType === 'First') {
812
- queryGroup[field.id] = {'$first': '$' + fieldPath.replace(/\.\$/g, '')};
813
- }
814
- else if (field.leafValueType === 'Last') {
815
- queryGroup[field.id] = {'$last': '$' + fieldPath.replace(/\.\$/g, '')};
816
- }
817
- else if (field.leafValueType === 'Unique') {
818
- queryGroup[field.id] = {'$addToSet': '$' + fieldPath.replace(/\.\$/g, '')};
819
- }
820
- else {
821
- queryGroup[field.id] = {'$push': {$ifNull: ['$' + fieldPath.replace(/\.\$/g, ''), null]}};
822
- }
823
- });
824
-
825
- customFields.forEach(cust => {
826
- if (cust.leafValueType === 'Average') {
827
- queryGroup[cust.selFieldId] = {'$avg': '$' + cust.selFieldId};
828
- }
829
- else if (cust.leafValueType === 'Sum') {
830
- queryGroup[cust.selFieldId] = {'$sum': '$' + cust.selFieldId};
831
- }
832
- else if (cust.leafValueType === 'Count') {
833
- if (reportType === 'Dated') {
834
- queryGroup[cust.selFieldId] = { '$sum': '$count_' + cust.selFieldId };
835
- }
836
- else {
837
- queryGroup[cust.selFieldId] = { '$push': '$count_' + cust.selFieldId };
838
- }
839
- }
840
- else if (cust.leafValueType === 'Minimum') {
841
- queryGroup[cust.selFieldId] = {'$min': '$' + cust.selFieldId};
842
- }
843
- else if (cust.leafValueType === 'Maximum') {
844
- queryGroup[cust.selFieldId] = {'$max': '$' + cust.selFieldId};
845
- }
846
- else if (cust.leafValueType === 'First') {
847
- queryGroup[cust.selFieldId] = {'$first': '$' + cust.selFieldId};
848
- }
849
- else if (cust.leafValueType === 'Last') {
850
- queryGroup[cust.selFieldId] = {'$last': '$' + cust.selFieldId};
851
- }
852
- else if (cust.leafValueType === 'Unique') {
853
- queryGroup[cust.selFieldId] = {'$addToSet': '$' + cust.selFieldId};
854
- }
855
- else {
856
- queryGroup[cust.selFieldId] = {'$push': {$ifNull: ['$' + cust.selFieldId, null]}};
857
- }
858
- });
859
-
860
- // Totals
861
- if (fieldsTotal.length) {
862
- fieldsTotal.forEach(total => {
863
- selectedFields.filter(a => a.fieldType === 'Number' || a.leafValueType === 'Count').forEach(field => {
864
- queryGroup[total.id + '_' + field.id] = {['$' + total.type] : '$' + total.id + '_' + field.id};
865
- });
866
-
867
- customFields.forEach(field => {
868
- queryGroup[total.id + '_' + field.selFieldId] = {['$' + total.type]: '$' + total.id + '_' + field.selFieldId};
869
- });
870
- });
871
- }
872
-
873
- query.push({$group: queryGroup});
874
- }
875
- else {
876
- //Projection
877
- let queryProjection = {
878
- _id: 1
879
- };
880
-
881
- selectedFields.forEach(field => {
882
- const fieldPath = (field && typeof field.fieldPath === 'string') ? field.fieldPath : '';
883
- if (!fieldPath && field.leafValueType !== 'Count') {
884
- return;
885
- }
886
-
887
- if (field.leafValueType === 'Average') {
888
- queryProjection[field.id] = buildArrayAwareAverageFieldExpr(fieldPath);
889
- }
890
- else if (field.leafValueType === 'Sum') {
891
- queryProjection[field.id] = buildArrayAwareNumericFieldExpr(fieldPath);
892
- }
893
- else if (field.leafValueType === 'Count') {
894
- queryProjection[field.id] = {$sum: 1};
895
- }
896
- else if (field.leafValueType === 'Minimum') {
897
- queryProjection[field.id] = {$min: '$' + fieldPath.replace(/\.\$/g, '')};
898
- }
899
- else if (field.leafValueType === 'Maximum') {
900
- queryProjection[field.id] = {$max: '$' + fieldPath.replace(/\.\$/g, '')};
901
- }
902
- else {
903
- queryProjection[field.id] = '$' + fieldPath.replace(/\.\$/g, '');
904
- }
905
- });
906
-
907
- customFields.forEach(cust => {
908
- if (cust === 'Average') {
909
- queryProjection[cust.selFieldId] = {$avg: '$' + cust.selFieldId};
910
- }
911
- else if (cust === 'Sum') {
912
- queryProjection[cust.selFieldId] = {$sum: '$' + cust.selFieldId};
913
- }
914
- else if (cust === 'Count') {
915
- queryProjection[cust.selFieldId] = {$sum: 1};
916
- }
917
- else if (cust === 'Minimum') {
918
- queryProjection[cust.selFieldId] = {$min: '$' + cust.selFieldId};
919
- }
920
- else if (cust === 'Maximum') {
921
- queryProjection[cust.selFieldId] = {$max: '$' + cust.selFieldId};
922
- }
923
- else {
924
- queryProjection[cust.selFieldId] = '$' + cust.selFieldId;
925
- }
926
- });
927
-
928
- //Totals
929
- if (fieldsTotal.length && (reportType === 'List' || reportType === 'Dated')) {
930
- fieldsTotal.forEach(total => {
931
- selectedFields.filter(a => a.fieldType === 'Number' || a.leafValueType === 'Count').forEach(field => {
932
- const totalFieldPath = total.id + '_' + field.id;
933
- if (total.type === 'sum') {
934
- queryProjection[totalFieldPath] = buildArrayAwareNumericFieldExpr(totalFieldPath);
935
- }
936
- else if (total.type === 'avg') {
937
- queryProjection[totalFieldPath] = buildArrayAwareAverageFieldExpr(totalFieldPath);
938
- }
939
- else {
940
- queryProjection[totalFieldPath] = {['$' + total.type]: '$' + totalFieldPath};
941
- }
942
- });
943
-
944
- customFields.forEach(field => {
945
- queryProjection[total.id + '_' + field.selFieldId] = {['$' + total.type]: '$' + total.id + '_' + field.selFieldId};
946
- });
947
- });
948
- }
949
-
950
- query.push({$project: queryProjection});
951
- }
952
-
953
- // SORT
954
- if (rootOptions.sort) {
955
- appendSafeSort(query, rootOptions.sort, selectedFields, customFields);
956
- }
957
-
958
- if (reportType === 'Group' && groupsRow.length > 1) {
959
- for (let i = groupsRow.length - 1; i >= 1; i--) {
960
- let projQuery = {_id: 1};
961
-
962
- projQuery[groupsRow[i].id] = {
963
- _id: {[groupsRow[i].id]: '$_id.' + groupsRow[i].id}
964
- };
965
-
966
- if (i === groupsRow.length - 1) {
967
- selectedFields.forEach(field => {
968
- projQuery[groupsRow[i].id][field.id] = '$' + field.id;
969
- });
970
-
971
- customFields.forEach(field => {
972
- projQuery[groupsRow[i].id][field.selFieldId] = '$' + field.selFieldId;
973
- });
974
- }
975
- else {
976
- projQuery[groupsRow[i].id][groupsRow[i + 1].id] = '$' + groupsRow[i + 1].id;
977
- }
978
-
979
- fieldsTotal.forEach(totField => {
980
- selectedFields.filter(b => b.fieldType === 'Number' || b.leafValueType === 'Count').forEach(selField => {
981
- projQuery[totField.id + '_' + selField.id] = 1;
982
- projQuery[groupsRow[i].id][totField.id + '_' + selField.id] = '$' + totField.id + '_' + selField.id;
983
- });
984
-
985
- customFields.forEach(field => {
986
- projQuery[groupsRow[i].id][totField.id + '_' + field.selFieldId] = '$' + totField.id + '_' + field.selFieldId;
987
- });
988
- });
989
-
990
- query.push({$project: projQuery});
991
-
992
- let grQuery = {
993
- _id: {}
994
- };
995
-
996
- for (let j = 0; j < i; j++) {
997
- grQuery._id[groupsRow[j].id] = '$_id.' + groupsRow[j].id;
998
- }
999
-
1000
- grQuery[groupsRow[i].id] = {
1001
- $push: '$' + groupsRow[i].id
1002
- };
1003
-
1004
- fieldsTotal.forEach(totField => {
1005
- selectedFields.filter(b => b.fieldType === 'Number' || b.leafValueType === 'Count').forEach(selField => {
1006
- grQuery[totField.id + '_' + selField.id] = {['$' + totField.type]: '$' + totField.id + '_' + selField.id};
1007
- });
1008
-
1009
- customFields.forEach(field => {
1010
- grQuery[totField.id + '_' + field.selFieldId] = {['$' + totField.type]: '$' + totField.id + '_' + field.selFieldId};
1011
- });
1012
- });
1013
-
1014
- query.push({$group: grQuery});
1015
-
1016
- if (rootOptions.sort) {
1017
- let sortKeys = Object.keys(rootOptions.sort);
1018
- let sort = {};
1019
-
1020
- let groupString = '';
1021
- groupsRow.forEach((row, groupIndex) => {
1022
- if (groupIndex > 0) {
1023
- groupString += 'gr_' + (groupIndex + 1) + '.';
1024
- }
1025
-
1026
- if (sortKeys.includes('_id.gr_' + (groupIndex + 1))) {
1027
- sort[groupString + '_id.gr_' + (groupIndex + 1)] = rootOptions.sort['_id.gr_' + (groupIndex + 1)];
1028
- }
1029
- else {
1030
- sort[groupString + '_id.gr_' + (groupIndex + 1)] = 1;
1031
- }
1032
- });
1033
-
1034
- sortKeys.filter(a => !a.startsWith('_id.gr_')).forEach(key => {
1035
- sort[groupString + key] = rootOptions.sort[key];
1036
- });
1037
-
1038
- appendSafeSort(query, sort, selectedFields, customFields);
1039
- }
1040
- }
1041
- }
1042
-
1043
- if (this.serverConfig['ROOT_URL'] === 'http://localhost:4200') {
1044
- console.log('_____________ query __________'); console.log(JSON.stringify(query, null, 2));
1045
- }
1046
-
1047
- const readPreference = this.serverConfig['ROOT_URL'] !== 'http://localhost:4200' ? 'secondary' : 'primary';
1048
- let queryResult = {
1049
- count: 0,
1050
- results: [],
1051
- totals: {}
1052
- };
1053
-
1054
- try {
1055
- queryResult = await executeReportBuilderQuery(modelCollection, query, rootOptions, fieldsTotal, selectedFields, customFields, readPreference);
1056
- }
1057
- catch (err) {
1058
- console.log(err);
1059
- }
1060
-
1061
- let tmpRes = [{
1062
- count: queryResult.count,
1063
- results: queryResult.results,
1064
- totals: queryResult.totals
1065
- }];
1066
-
1067
- return tmpRes;
1068
- }
1069
- },
1070
- workerTaskWeight: 2
1071
- }
1072
- });
1073
- }
1074
-
1075
- function coerceReportBuilderNumeric(value: any): number {
1076
- if (Array.isArray(value)) {
1077
- return value.map(v => coerceReportBuilderNumeric(v)).reduce((a, b) => a + b, 0);
1078
- }
1079
-
1080
- const num = typeof value === 'number' ? value : (value !== null && value !== undefined ? Number(value) : 0);
1081
- return isNaN(num) ? 0 : num;
1082
- }
1083
-
1084
- function buildReportBuilderTotalsFieldKeySets(fieldsTotal: any[] = [], selectedFields: any[] = [], customFields: any[] = []) {
1085
- const numericSelectedFields = selectedFields.filter(field => field?.fieldType === 'Number' || field?.leafValueType === 'Count');
1086
- const sumKeys = new Set<string>();
1087
- const avgKeys = new Set<string>();
1088
-
1089
- fieldsTotal.forEach(totalField => {
1090
- numericSelectedFields.forEach(selectedField => {
1091
- const key = `${totalField.id}_${selectedField.id}`;
1092
- if (!key) {
1093
- return;
1094
- }
1095
-
1096
- if (totalField.type === 'sum') {
1097
- sumKeys.add(key);
1098
- }
1099
- else if (totalField.type === 'avg') {
1100
- avgKeys.add(key);
1101
- }
1102
- });
1103
-
1104
- customFields.forEach(customField => {
1105
- const customId = customField?.selFieldId;
1106
- if (!customId) {
1107
- return;
1108
- }
1109
-
1110
- const key = `${totalField.id}_${customId}`;
1111
- if (totalField.type === 'sum') {
1112
- sumKeys.add(key);
1113
- }
1114
- else if (totalField.type === 'avg') {
1115
- avgKeys.add(key);
1116
- }
1117
- });
1118
- });
1119
-
1120
- return {sumKeys, avgKeys};
1121
- }
1122
-
1123
- function buildReportBuilderTotalsFromSums(fieldsTotal: any[] = [], selectedFields: any[] = [], customFields: any[] = [], sumValues: Record<string, number> = {}, count = 0) {
1124
- const numericSelectedFields = selectedFields.filter(field => field?.fieldType === 'Number' || field?.leafValueType === 'Count');
1125
- const totals = {};
1126
-
1127
- fieldsTotal.forEach(totalField => {
1128
- numericSelectedFields.forEach(selectedField => {
1129
- const key = `${totalField.id}_${selectedField.id}`;
1130
- const sum = coerceReportBuilderNumeric(sumValues[key]);
1131
-
1132
- if (totalField.type === 'sum') {
1133
- totals[key] = sum;
1134
- }
1135
- else if (totalField.type === 'avg') {
1136
- totals[key] = count ? round(sum / count) : 0;
1137
- }
1138
- });
1139
-
1140
- customFields.forEach(customField => {
1141
- const customId = customField?.selFieldId;
1142
- if (!customId) {
1143
- return;
1144
- }
1145
-
1146
- const key = `${totalField.id}_${customId}`;
1147
- const sum = coerceReportBuilderNumeric(sumValues[key]);
1148
-
1149
- if (totalField.type === 'sum') {
1150
- totals[key] = sum;
1151
- }
1152
- else if (totalField.type === 'avg') {
1153
- totals[key] = count ? round(sum / count) : 0;
1154
- }
1155
- });
1156
- });
1157
-
1158
- return totals;
1159
- }
1160
-
1161
- function buildReportBuilderNumericSumExpr(fieldKey: string) {
1162
- const fieldExpr = '$' + fieldKey;
1163
-
1164
- return {
1165
- $cond: [
1166
- {$isArray: fieldExpr},
1167
- {
1168
- $reduce: {
1169
- input: fieldExpr,
1170
- initialValue: 0,
1171
- in: {
1172
- $add: [
1173
- '$$value',
1174
- {$convert: {input: '$$this', to: 'double', onError: 0, onNull: 0}}
1175
- ]
1176
- }
1177
- }
1178
- },
1179
- {$convert: {input: fieldExpr, to: 'double', onError: 0, onNull: 0}}
1180
- ]
1181
- };
1182
- }
1183
-
1184
- async function executeReportBuilderQuery(modelCollection: any, query: any[], rootOptions: PaginationOptions, fieldsTotal: any[] = [], selectedFields: any[] = [], customFields: any[] = [], readPreference: string = 'primary') {
1185
- const aggregateOptions = {
1186
- allowDiskUse: true,
1187
- readPreference
1188
- };
1189
-
1190
- const {sumKeys, avgKeys} = buildReportBuilderTotalsFieldKeySets(fieldsTotal, selectedFields, customFields);
1191
- const allTotalKeys = new Set<string>([...sumKeys, ...avgKeys]);
1192
-
1193
- if (rootOptions?.limit && rootOptions.limit > 0) {
1194
- const resultsFacet: any[] = [];
1195
- if (rootOptions.skip && rootOptions.skip > 0) {
1196
- resultsFacet.push({$skip: rootOptions.skip});
1197
- }
1198
- resultsFacet.push({$limit: rootOptions.limit});
1199
-
1200
- const facet: any = {
1201
- results: resultsFacet,
1202
- count: [{$count: 'count'}],
1203
- totals: []
1204
- };
1205
-
1206
- if (allTotalKeys.size) {
1207
- const totalsGroup = {
1208
- _id: null,
1209
- __count: {$sum: 1}
1210
- };
1211
-
1212
- Array.from(allTotalKeys).forEach(key => {
1213
- totalsGroup[key] = {$sum: buildReportBuilderNumericSumExpr(key)};
1214
- });
1215
-
1216
- facet.totals = [
1217
- {$group: totalsGroup}
1218
- ];
1219
- }
1220
-
1221
- const facetedQuery = query.concat([
1222
- {$facet: facet}
1223
- ]);
1224
- const facetedResult = await modelCollection.aggregate(facetedQuery, aggregateOptions, true);
1225
- const facetDoc = Array.isArray(facetedResult) && facetedResult.length ? facetedResult[0] : {};
1226
- const count = facetDoc?.count?.[0]?.count || 0;
1227
- const results = Array.isArray(facetDoc?.results) ? facetDoc.results : [];
1228
- const totalsDoc = facetDoc?.totals?.[0] || {};
1229
- const sumValues = {};
1230
-
1231
- Array.from(allTotalKeys).forEach(key => {
1232
- sumValues[key] = coerceReportBuilderNumeric(totalsDoc[key]);
1233
- });
1234
-
1235
- const totals = buildReportBuilderTotalsFromSums(fieldsTotal, selectedFields, customFields, sumValues, count);
1236
-
1237
- return {count, results, totals};
1238
- }
1239
-
1240
- const fullResult = await modelCollection.aggregate(query, aggregateOptions, true);
1241
- const rows = Array.isArray(fullResult) ? fullResult : [];
1242
- const sumValues = {};
1243
-
1244
- Array.from(allTotalKeys).forEach(key => {
1245
- sumValues[key] = rows.map(row => coerceReportBuilderNumeric(row ? row[key] : 0)).reduce((a, b) => a + b, 0);
1246
- });
1247
-
1248
- let results = rows;
1249
- if (rootOptions?.skip && rootOptions.skip > 0) {
1250
- results = results.slice(rootOptions.skip);
1251
- }
1252
- if (rootOptions?.limit && rootOptions.limit > 0) {
1253
- results = results.slice(0, rootOptions.limit);
1254
- }
1255
-
1256
- const totals = buildReportBuilderTotalsFromSums(fieldsTotal, selectedFields, customFields, sumValues, rows.length);
1257
-
1258
- return {
1259
- count: rows.length,
1260
- results,
1261
- totals
1262
- };
1263
- }
1264
-
1265
- function buildCollectionTree(collectionName: string, lookupNode?: any) {
1266
- let lookupSchemaData = getReportLookupSchemas(collectionName).sort((a, b) => a.collection_name.localeCompare(b.collection_name));
1267
- let lookupSchemaTree = lookupSchemaData.find(a => a.is_root === true)?.tree || {};
1268
-
1269
- let treeItems = buildTree(collectionName, lookupSchemaTree, lookupNode);
1270
-
1271
- attachLookupChildren(treeItems, lookupSchemaData, lookupSchemaTree);
1272
-
1273
- return {isLeaf: false, children: treeItems};
1274
- }
1275
-
1276
- function attachLookupChildren(treeItems: any[], lookupSchemaData: any[], lookupSchemaTree: any) {
1277
- Object.keys(lookupSchemaTree || {}).filter(a => a.endsWith('(Lookup)')).forEach(lookup => {
1278
- let field = findFieldByPath(treeItems, lookup);
1279
-
1280
- if (field) {
1281
- let lookupTree = lookupSchemaData.find(a => a.collection_name === lookupSchemaTree[lookup].lookup_collection);
1282
-
1283
- if (lookupTree) {
1284
- let childTree = buildTree(lookupSchemaTree[lookup].lookup_collection, lookupTree.tree, field);
1285
- attachLookupChildren(childTree, lookupSchemaData, lookupTree.tree);
1286
- field.children = childTree;
1287
- }
1288
- }
1289
- });
1290
- }
1291
-
1292
- function findFieldByPath(treeItems: any[], lookupPath: string) {
1293
- let fieldPath = lookupPath.split('.');
1294
- let field = null;
1295
-
1296
- fieldPath.forEach(path => {
1297
- if (!field) {
1298
- field = treeItems.find(a => a.fieldName === path);
1299
- }
1300
- else if (field.children) {
1301
- field = field.children.find(a => a.fieldName === path);
1302
- }
1303
- });
1304
-
1305
- return field;
1306
- }
1307
-
1308
- function buildJoinLookupNode(collection_root: string, join: ReportBuilderCollectionJoin, alias: string) {
1309
- return {
1310
- collection_name: collection_root,
1311
- columnName: alias,
1312
- fieldName: alias,
1313
- fieldType: 'Lookup',
1314
- fieldTypeName: 'Lookup',
1315
- distinctFieldValues: [],
1316
- fieldPath: alias,
1317
- fieldPathName: alias,
1318
- path: alias,
1319
- lookup_collection: join.collection,
1320
- lookup_local_key: join.local_key,
1321
- lookup_foreign_key: join.foreign_key,
1322
- lookup_as: alias,
1323
- text: alias,
1324
- value: alias,
1325
- isLeaf: false,
1326
- isActive: false,
1327
- isSelected: false,
1328
- is_join: false,
1329
- depth: 0,
1330
- leafValueType: '',
1331
- leafFormatType: '',
1332
- children: []
1333
- };
1334
- }
1335
-
1336
- function buildJoinAlias(join: ReportBuilderCollectionJoin, joinIndex: number) {
1337
- if (join.alias && join.alias.trim()) {
1338
- return join.alias.trim();
1339
- }
1340
-
1341
- let joinName = toTitleCase(join.collection.replace(/\_/g, ' ').replace(/-/g, ' '));
1342
- let joinPath = toTitleCase(join.local_key.replace(/\.\$\./g, ' -> ').replace(/\./g, ' -> ').replace(/\_/g, ' '));
1343
-
1344
- return `${joinName} (${joinPath || 'Join'}) (Lookup ${joinIndex + 1})`;
1345
- }
1346
-
1347
- function buildArrayAwareNumericFieldExpr(fieldPath: string) {
1348
- const normalizedFieldPath = (fieldPath || '').replace(/\.\$/g, '');
1349
- if (!normalizedFieldPath) {
1350
- return 0;
1351
- }
1352
- const fieldExpr = '$' + normalizedFieldPath;
1353
-
1354
- return {
1355
- $cond: [
1356
- {$isArray: fieldExpr},
1357
- {$sum: {$ifNull: [fieldExpr, []]}},
1358
- {$ifNull: [fieldExpr, 0]}
1359
- ]
1360
- };
1361
- }
1362
-
1363
- function buildArrayAwareAverageFieldExpr(fieldPath: string) {
1364
- const normalizedFieldPath = (fieldPath || '').replace(/\.\$/g, '');
1365
- if (!normalizedFieldPath) {
1366
- return 0;
1367
- }
1368
- const fieldExpr = '$' + normalizedFieldPath;
1369
-
1370
- return {
1371
- $cond: [
1372
- {$isArray: fieldExpr},
1373
- {
1374
- $cond: [
1375
- {$gt: [{$size: {$ifNull: [fieldExpr, []]}}, 0]},
1376
- {$avg: {$ifNull: [fieldExpr, []]}},
1377
- 0
1378
- ]
1379
- },
1380
- {$ifNull: [fieldExpr, 0]}
1381
- ]
1382
- };
1383
- }
1384
-
1385
- function appendSafeSort(query: any[], sortSpec: any, selectedFields: any[] = [], customFields: any[] = []) {
1386
- if (!sortSpec || !Object.keys(sortSpec).length) {
1387
- return;
1388
- }
1389
-
1390
- const safeAddFields = {};
1391
- const safeSort = {};
1392
-
1393
- Object.keys(sortSpec).forEach(key => {
1394
- if (key.startsWith('_id.')) {
1395
- safeSort[key] = sortSpec[key];
1396
- return;
1397
- }
1398
-
1399
- if (isArrayLikeField(key, selectedFields, customFields)) {
1400
- const safeKey = 'sort_' + key.replace(/\./g, '_');
1401
- safeAddFields[safeKey] = {
1402
- $cond: [
1403
- {$isArray: '$' + key},
1404
- {$size: {$ifNull: ['$' + key, []]}},
1405
- '$' + key
1406
- ]
1407
- };
1408
- safeSort[safeKey] = sortSpec[key];
1409
- }
1410
- else {
1411
- safeSort[key] = sortSpec[key];
1412
- }
1413
- });
1414
-
1415
- if (Object.keys(safeAddFields).length) {
1416
- query.push({$addFields: safeAddFields});
1417
- }
1418
-
1419
- if (Object.keys(safeSort).length) {
1420
- query.push({$sort: safeSort});
1421
- }
1422
- }
1423
-
1424
- function isArrayLikeField(sortKey: string, selectedFields: any[] = [], customFields: any[] = []) {
1425
- const field = selectedFields.find(a => a.id === sortKey);
1426
- const custom = customFields.find(a => a.selFieldId === sortKey);
1427
-
1428
- const leafValueType = field ? field.leafValueType : (custom ? custom.leafValueType : '');
1429
-
1430
- return leafValueType !== 'Average' &&
1431
- leafValueType !== 'Sum' &&
1432
- leafValueType !== 'Count' &&
1433
- leafValueType !== 'Minimum' &&
1434
- leafValueType !== 'Maximum' &&
1435
- leafValueType !== 'First' &&
1436
- leafValueType !== 'Last' &&
1437
- leafValueType !== 'Unique';
1438
- }
1439
-
1440
- function collectLookupAliases(selectedFields: any[] = [], filterArrayFields: any[] = [], groupsRow: any[] = []) {
1441
- const aliases = new Set<string>();
1442
-
1443
- const addAlias = (alias: string) => {
1444
- const normalized = normalizeLookupAlias(alias);
1445
- if (normalized) {
1446
- aliases.add(normalized);
1447
- }
1448
- };
1449
-
1450
- (selectedFields || []).forEach(field => {
1451
- if (field && (field.lookup_collection || (field.fieldPath && field.fieldPath.includes('(Lookup')))) {
1452
- addAlias(field.lookup_as || extractLookupAlias(field.fieldPath));
1453
- }
1454
- });
1455
-
1456
- (filterArrayFields || []).forEach(field => {
1457
- if (field && (field.lookup_collection || (field.fieldPath && field.fieldPath.includes('(Lookup')))) {
1458
- addAlias(field.lookup_as || extractLookupAlias(field.fieldPath));
1459
- }
1460
- });
1461
-
1462
- (groupsRow || []).forEach(group => {
1463
- if (group?.treeItem && (group.treeItem.lookup_collection || (group.treeItem.fieldPath && group.treeItem.fieldPath.includes('(Lookup')))) {
1464
- addAlias(group.treeItem.lookup_as || extractLookupAlias(group.treeItem.fieldPath));
1465
- }
1466
- });
1467
-
1468
- return aliases;
1469
- }
1470
-
1471
- function splitFiltersByLookup(filters: any[] = [], lookupAliases: Set<string> = new Set()) {
1472
- const rootFilters = [];
1473
- const lookupFilters = [];
1474
-
1475
- (filters || []).forEach(filter => {
1476
- const summary = summarizeFilterTargets(filter, lookupAliases);
1477
-
1478
- if (summary.hasLookup) {
1479
- // If the filter mixes root + lookup, keep it together in lookupFilters so it stays a single $match/$or
1480
- lookupFilters.push(filter);
1481
- }
1482
- else {
1483
- rootFilters.push(filter);
1484
- }
1485
- });
1486
-
1487
- return {rootFilters, lookupFilters};
1488
- }
1489
-
1490
- function summarizeFilterTargets(filter: any, lookupAliases: Set<string>) {
1491
- const result = {hasLookup: false, hasRoot: false};
1492
-
1493
- if (!filter || typeof filter !== 'object') {
1494
- return result;
1495
- }
1496
-
1497
- Object.keys(filter).forEach(key => {
1498
- if (key === '$or' || key === '$and') {
1499
- const branch = filter[key];
1500
- if (Array.isArray(branch)) {
1501
- branch.forEach(child => {
1502
- const childSummary = summarizeFilterTargets(child, lookupAliases);
1503
- result.hasLookup = result.hasLookup || childSummary.hasLookup;
1504
- result.hasRoot = result.hasRoot || childSummary.hasRoot;
1505
- });
1506
- }
1507
- }
1508
- else {
1509
- const isLookup = isLookupPath(key, lookupAliases);
1510
- if (isLookup) {
1511
- result.hasLookup = true;
1512
- }
1513
- else {
1514
- result.hasRoot = true;
1515
- }
1516
- }
1517
- });
1518
-
1519
- return result;
1520
- }
1521
-
1522
- function isLookupPath(path: string, lookupAliases: Set<string>) {
1523
- if (!path) {
1524
- return false;
1525
- }
1526
-
1527
- const normalizedPath = path.replace(/\.\$/g, '');
1528
-
1529
- if (normalizedPath.includes('(Lookup')) {
1530
- return true;
1531
- }
1532
-
1533
- const rootKey = normalizedPath.split('.')[0];
1534
- return lookupAliases.has(rootKey);
1535
- }
1536
-
1537
- function extractLookupAlias(path: string) {
1538
- if (!path || !path.includes('(Lookup')) {
1539
- return '';
1540
- }
1541
-
1542
- return path.replace(/\.\$/g, '').split('.')[0];
1543
- }
1544
-
1545
- function normalizeLookupAlias(alias: string) {
1546
- return (alias || '').replace(/\.\$/g, '').trim();
1547
- }
1548
-
1549
- function collectRootDatePaths(collectionName: string) {
1550
- const out = new Set<string>();
1551
- const collection = ResolveIOServer.getMongoManager().collection(collectionName);
1552
- const schema = collection?.rbSchema || {};
1553
-
1554
- Object.keys(schema).forEach(path => {
1555
- if (schema[path]?.rbType === 'Date') {
1556
- out.add(path.replace(/\.\$/g, ''));
1557
- }
1558
- });
1559
-
1560
- return out;
1561
- }
1562
-
1563
- function coerceDateFilters(filters: any[] = [], datePaths: Set<string>) {
1564
- const coerceDateValue = (value: any): any => {
1565
- if (value instanceof Date) {
1566
- return value;
1567
- }
1568
- if (Array.isArray(value)) {
1569
- return value.map(coerceDateValue);
1570
- }
1571
- if (typeof value === 'string' || typeof value === 'number') {
1572
- const parsed = new Date(value);
1573
- if (!isNaN(parsed.getTime())) {
1574
- return parsed;
1575
- }
1576
- }
1577
- return value;
1578
- };
1579
-
1580
- const coerceFilterObject = (obj: any): any => {
1581
- if (!obj || typeof obj !== 'object') {
1582
- return obj;
1583
- }
1584
-
1585
- if (Array.isArray(obj)) {
1586
- obj.forEach(coerceFilterObject);
1587
- return obj;
1588
- }
1589
-
1590
- if (obj.$or) {
1591
- obj.$or = obj.$or.map(coerceFilterObject).filter(Boolean);
1592
- return obj;
1593
- }
1594
-
1595
- if (obj.$and) {
1596
- obj.$and = obj.$and.map(coerceFilterObject).filter(Boolean);
1597
- return obj;
1598
- }
1599
-
1600
- Object.keys(obj).forEach(key => {
1601
- if (key.startsWith('$')) {
1602
- return;
1603
- }
1604
-
1605
- const normalizedKey = key.replace(/\.\$/g, '');
1606
- if (!datePaths.has(normalizedKey)) {
1607
- return;
1608
- }
1609
-
1610
- const value = obj[key];
1611
- if (value && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {
1612
- Object.keys(value).forEach(op => {
1613
- if (op.startsWith('$')) {
1614
- value[op] = coerceDateValue(value[op]);
1615
- }
1616
- });
1617
- }
1618
- else {
1619
- obj[key] = coerceDateValue(value);
1620
- }
1621
- });
1622
-
1623
- return obj;
1624
- };
1625
-
1626
- (filters || []).forEach(coerceFilterObject);
1627
- }
1628
-
1629
- // Helpers to support union-based joins (collection_joins without local/foreign key)
1630
- function stripAliasFromPath(path: string, alias: string) {
1631
- if (!path) {
1632
- return path;
1633
- }
1634
-
1635
- const cleanAlias = alias.replace(/\.\$/g, '');
1636
- const cleanPath = path.replace(/\.\$/g, '');
1637
-
1638
- if (cleanPath.startsWith(cleanAlias + '.')) {
1639
- return cleanPath.substring(cleanAlias.length + 1);
1640
- }
1641
-
1642
- return cleanPath;
1643
- }
1644
-
1645
- function buildCollectionPipeline(collectionName: string, alias: string, selectedFields: any[] = [], filters: any[] = [], filterArrays: any[] = [], isRoot = false) {
1646
- const pipeline: any[] = [];
1647
- const matchClauses: any[] = [];
1648
- const matchArrayClauses: any[] = [];
1649
-
1650
- const targetAlias = isRoot ? '' : alias.replace(/\.\$/g, '');
1651
-
1652
- const mapFilterForCollection = (filterObj: any, toArray: boolean) => {
1653
- if (!filterObj || typeof filterObj !== 'object') {
1654
- return;
1655
- }
1656
-
1657
- const mapped = mapFilterPaths(filterObj, targetAlias, isRoot);
1658
- if (mapped) {
1659
- (toArray ? matchArrayClauses : matchClauses).push(mapped);
1660
- }
1661
- };
1662
-
1663
- (filters || []).forEach(f => mapFilterForCollection(f, false));
1664
- (filterArrays || []).forEach(f => mapFilterForCollection(f, true));
1665
-
1666
- if (matchClauses.length || matchArrayClauses.length) {
1667
- const match: any = {$and: [...matchClauses, ...matchArrayClauses]};
1668
- pipeline.push({$match: match});
1669
-
1670
- if (process.env.NODE_ENV !== 'production') {
1671
- console.info('RB debug: collection pipeline match', {
1672
- collection: collectionName,
1673
- alias: targetAlias || collectionName,
1674
- matchJson: JSON.stringify(match, null, 2)
1675
- });
1676
- }
1677
- }
1678
-
1679
- // Projection mapping selected fields for this collection
1680
- const projection: any = {_id: 1, __rb_collection: targetAlias || collectionName};
1681
- (selectedFields || [])
1682
- .filter(f => isFieldForCollection(f, collectionName, targetAlias))
1683
- .forEach(f => {
1684
- const mappedPath = stripAliasFromPath(f.fieldPath || '', targetAlias || f.lookup_as || '');
1685
- if (f.leafValueType === 'Average') {
1686
- projection[f.id] = buildArrayAwareAverageFieldExpr(mappedPath);
1687
- }
1688
- else if (f.leafValueType === 'Sum') {
1689
- projection[f.id] = buildArrayAwareNumericFieldExpr(mappedPath);
1690
- }
1691
- else if (f.leafValueType === 'Count') {
1692
- projection[f.id] = {$sum: 1};
1693
- }
1694
- else if (f.leafValueType === 'Minimum') {
1695
- projection[f.id] = {$min: '$' + mappedPath};
1696
- }
1697
- else if (f.leafValueType === 'Maximum') {
1698
- projection[f.id] = {$max: '$' + mappedPath};
1699
- }
1700
- else {
1701
- projection[f.id] = '$' + mappedPath;
1702
- }
1703
- });
1704
-
1705
- pipeline.push({$project: projection});
1706
-
1707
- return pipeline;
1708
- }
1709
-
1710
- function isFieldForCollection(field: any, collectionName: string, alias: string) {
1711
- if (!field) {
1712
- return false;
1713
- }
1714
-
1715
- // Root collection check
1716
- if (!alias) {
1717
- return field.collection_name === collectionName && (!field.lookup_collection || field.lookup_collection === '');
1718
- }
1719
-
1720
- // Joined collection check
1721
- return field.lookup_collection === collectionName || (field.lookup_as && field.lookup_as.replace(/\.\$/g, '') === alias);
1722
- }
1723
-
1724
- function mapFilterPaths(filter: any, alias: string, isRoot: boolean) {
1725
- if (!filter || typeof filter !== 'object') {
1726
- return null;
1727
- }
1728
-
1729
- if (Array.isArray(filter)) {
1730
- return filter.map(f => mapFilterPaths(f, alias, isRoot));
1731
- }
1732
-
1733
- if (filter.$or) {
1734
- const mapped = filter.$or.map(f => mapFilterPaths(f, alias, isRoot)).filter(Boolean);
1735
- return mapped.length ? {$or: mapped} : null;
1736
- }
1737
-
1738
- if (filter.$and) {
1739
- const mapped = filter.$and.map(f => mapFilterPaths(f, alias, isRoot)).filter(Boolean);
1740
- return mapped.length ? {$and: mapped} : null;
1741
- }
1742
-
1743
- const out: any = {};
1744
- Object.keys(filter).forEach(key => {
1745
- const cleanKey = key.replace(/\.\$/g, '');
1746
-
1747
- if (!alias) {
1748
- // root collection: ignore keys that start with a lookup alias
1749
- if (cleanKey.includes('(Lookup')) {
1750
- return;
1751
- }
1752
- if (lookupAliasMatches(cleanKey, alias)) {
1753
- return;
1754
- }
1755
- out[cleanKey] = filter[key];
1756
- }
1757
- else {
1758
- if (cleanKey.startsWith(alias + '.')) {
1759
- out[cleanKey.substring(alias.length + 1)] = filter[key];
1760
- }
1761
- }
1762
- });
1763
-
1764
- return Object.keys(out).length ? out : null;
1765
- }
1766
-
1767
- function lookupAliasMatches(path: string, alias: string) {
1768
- if (!path || !alias) {
1769
- return false;
1770
- }
1771
- return path.startsWith(alias + '.') || path === alias;
1772
- }
1773
-
1774
- function buildLayoutSortSpec(sortSpec: any = {}, selectedFields: any[] = []) {
1775
- const outSort: any = {...sortSpec};
1776
- const addFields: any = {};
1777
-
1778
- Object.keys(sortSpec || {}).forEach(key => {
1779
- if (!key.startsWith('layout_col_')) {
1780
- return;
1781
- }
1782
-
1783
- const normalized = key.replace(/^layout_col_/, '');
1784
- const parts = normalized.split('_');
1785
- const idx = parseInt(parts[parts.length - 1], 10);
1786
-
1787
- if (isNaN(idx)) {
1788
- return;
1789
- }
1790
-
1791
- const layoutFields = (selectedFields || []).filter(f => typeof f?.layoutColumnIndex !== 'undefined' && f.layoutColumnIndex === idx).map(f => f.id).filter(Boolean);
1792
-
1793
- if (!layoutFields.length) {
1794
- return;
1795
- }
1796
-
1797
- // Build nested $ifNull chain to coalesce across layout column targets
1798
- let coalesce: any = '';
1799
- for (let i = layoutFields.length - 1; i >= 0; i--) {
1800
- const fieldPath = '$' + layoutFields[i];
1801
- if (i === layoutFields.length - 1) {
1802
- coalesce = fieldPath;
1803
- }
1804
- else {
1805
- coalesce = {$ifNull: [fieldPath, coalesce]};
1806
- }
1807
- }
1808
-
1809
- const sortKey = 'sort_' + key;
1810
- addFields[sortKey] = coalesce;
1811
-
1812
- delete outSort[key];
1813
- outSort[sortKey] = sortSpec[key];
1814
- });
1815
-
1816
- return {sortSpec: outSort, addFields};
1817
- }
1818
-
1819
- function remapFiltersToIds(filters: any[] = [], selectedFields: any[] = []) {
1820
- const byPath = new Map<string, string>();
1821
- const layoutMap = new Map<number, string[]>();
1822
- (selectedFields || []).forEach(f => {
1823
- if (f?.fieldPath && f?.id) {
1824
- byPath.set(f.fieldPath.replace(/\.\$/g, ''), f.id);
1825
- }
1826
-
1827
- if (typeof f?.layoutColumnIndex !== 'undefined') {
1828
- const arr = layoutMap.get(f.layoutColumnIndex) || [];
1829
- arr.push(f.id);
1830
- layoutMap.set(f.layoutColumnIndex, arr);
1831
- }
1832
- });
1833
-
1834
- const remapObj = (obj: any): any => {
1835
- if (!obj || typeof obj !== 'object') {
1836
- return obj;
1837
- }
1838
-
1839
- if (Array.isArray(obj)) {
1840
- return obj.map(remapObj);
1841
- }
1842
-
1843
- if (obj.$or) {
1844
- const mapped = obj.$or.map(remapObj).filter(Boolean);
1845
- return mapped.length ? {$or: mapped} : null;
1846
- }
1847
-
1848
- if (obj.$and) {
1849
- const mapped = obj.$and.map(remapObj).filter(Boolean);
1850
- return mapped.length ? {$and: mapped} : null;
1851
- }
1852
-
1853
- const out: any = {};
1854
- Object.keys(obj).forEach(key => {
1855
- const cleanKey = key.replace(/\.\$/g, '');
1856
- if (cleanKey.startsWith('layout_col_')) {
1857
- // map to all field ids in that layout column index
1858
- const normalized = cleanKey.replace(/^layout_col_/, '');
1859
- const parts = normalized.split('_');
1860
- const idx = parseInt(parts[parts.length - 1], 10);
1861
- const targets = layoutMap.get(idx) || [];
1862
-
1863
- if (targets.length) {
1864
- const mappedVal = remapObj(obj[key]);
1865
- out.$or = targets.map(id => ({[id]: mappedVal}));
1866
- return;
1867
- }
1868
- }
1869
-
1870
- const mappedKey = byPath.get(cleanKey) || cleanKey;
1871
- out[mappedKey] = remapObj(obj[key]);
1872
- });
1873
-
1874
- return Object.keys(out).length ? out : null;
1875
- };
1876
-
1877
- return (filters || []).map(remapObj).filter(Boolean);
1878
- }
1879
-
1880
- function expandLayoutColumnFilters(filters: any[] = [], selectedFields: any[] = []) {
1881
- const layoutCache: {[key: string]: string[]} = {};
1882
-
1883
- const resolveLayoutPaths = (layoutKey: string) => {
1884
- if (layoutCache[layoutKey]) {
1885
- return layoutCache[layoutKey];
1886
- }
1887
-
1888
- let paths: string[] = [];
1889
- const normalized = (layoutKey || '').replace(/^layout_col_/, '');
1890
- const parts = normalized.split('_');
1891
- const idx = parseInt(parts[parts.length - 1], 10);
1892
-
1893
- if (!isNaN(idx)) {
1894
- paths = (selectedFields || [])
1895
- .filter(f => f && (f.layoutColumnIndex === idx || (f.id || '').includes(`f_layout_${idx}_`)))
1896
- .map(f => f.fieldPath ? f.fieldPath.replace(/\.\$/g, '') : '')
1897
- .filter(p => !!p);
1898
- }
1899
-
1900
- layoutCache[layoutKey] = paths;
1901
- return paths;
1902
- };
1903
-
1904
- const expandFilterObject = (obj: any): any => {
1905
- if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
1906
- return obj;
1907
- }
1908
-
1909
- if (obj.$or) {
1910
- return {$or: obj.$or.map(expandFilterObject)};
1911
- }
1912
-
1913
- if (obj.$and) {
1914
- return {$and: obj.$and.map(expandFilterObject)};
1915
- }
1916
-
1917
- let expanded: any = {};
1918
- let replaced = false;
1919
-
1920
- Object.keys(obj).forEach(key => {
1921
- const val = obj[key];
1922
-
1923
- if (key.startsWith('layout_col_')) {
1924
- const paths = resolveLayoutPaths(key);
1925
-
1926
- if (paths.length) {
1927
- replaced = true;
1928
- expanded.$or = paths.map(path => ({[path]: val}));
1929
- // Use console to trace layout filter expansion without requiring a logger on ResolveIOServer
1930
- console.info('RB layout filter expansion', {layoutKey: key, paths});
1931
- }
1932
- else {
1933
- console.warn('RB layout filter expansion failed', {layoutKey: key, selectedFields});
1934
- }
1935
- }
1936
- else {
1937
- expanded[key] = val;
1938
- }
1939
- });
1940
-
1941
- return replaced ? expanded : obj;
1942
- };
1943
-
1944
- return (filters || []).map(expandFilterObject);
1945
- }
1946
-
1947
- type ReportBuilderAiPayload = {
1948
- prompt?: string;
1949
- report_type?: string;
1950
- report_name?: string;
1951
- collection_root?: string;
1952
- collection_joins?: Array<{ collection?: string; alias?: string }>;
1953
- id_date_field?: string;
1954
- date_interval?: string;
1955
- permission_view?: string;
1956
- allow_mongo_read?: boolean;
1957
- mongo?: {
1958
- database?: string;
1959
- databases?: string[];
1960
- access?: string;
1961
- readonly?: boolean;
1962
- };
1963
- available_fields?: Array<{
1964
- field_path?: string;
1965
- field_path_name?: string;
1966
- field_type?: string;
1967
- collection_name?: string;
1968
- }>;
1969
- available_collections?: Array<{ collection?: string; name?: string }>;
1970
- id_client?: string;
1971
- guardrails?: boolean;
1972
- config?: {
1973
- model?: string;
1974
- temperature?: number;
1975
- max_tokens?: number;
1976
- response_format?: string;
1977
- reasoning_effort?: string;
1978
- timeout_ms?: number;
1979
- };
1980
- field_limit?: number;
1981
- };
1982
-
1983
- type ReportBuilderAiResult = {
1984
- blocked?: boolean;
1985
- response?: string;
1986
- reason?: string;
1987
- patch?: any;
1988
- notes?: string;
1989
- usage?: {
1990
- input_tokens: number;
1991
- output_tokens: number;
1992
- total_tokens: number;
1993
- };
1994
- model?: string;
1995
- };
1996
-
1997
- type GuardrailResult = { blocked: boolean; response: string; reason: string };
1998
- type ReportTypeResolution = {
1999
- reportType: string;
2000
- inferenceReason: string;
2001
- inferredDateInterval: string;
2002
- };
2003
-
2004
- async function executeReportBuilderAi(payload: ReportBuilderAiPayload, context: any): Promise<ReportBuilderAiResult> {
2005
- const input = payload || {};
2006
- const prompt = normalizeOptionalString(input.prompt);
2007
- if (!prompt) {
2008
- throw new Error('Prompt is required.');
2009
- }
2010
-
2011
- const isSuperAdmin = await resolveIsSuperAdmin(context?.id_user);
2012
- const guardrailsEnabled = input.guardrails !== false && !isSuperAdmin;
2013
- if (guardrailsEnabled) {
2014
- const guardrail = evaluateReportBuilderGuardrails(prompt);
2015
- if (guardrail?.blocked) {
2016
- return guardrail;
2017
- }
2018
- }
2019
-
2020
- const reportTypeResolution = resolveReportTypeForPrompt(input.report_type, prompt);
2021
- const reportType = reportTypeResolution.reportType;
2022
- const collectionRoot = normalizeOptionalString(input.collection_root);
2023
- const collectionJoins = Array.isArray(input.collection_joins) ? input.collection_joins : [];
2024
- const idDateField = normalizeOptionalString(input.id_date_field);
2025
- const dateInterval = normalizeDateInterval(input.date_interval) || reportTypeResolution.inferredDateInterval;
2026
-
2027
- const collections = sanitizeCollections(input.available_collections || []);
2028
- const fieldLimit = normalizeFieldLimit(input.field_limit);
2029
- let resolvedFields = sanitizeFields(input.available_fields || []);
2030
- let usedFallbackFields = false;
2031
-
2032
- if (!resolvedFields.length && collections.length) {
2033
- const derivedFields = deriveFieldsFromCollections(collections);
2034
- resolvedFields = sanitizeFields(derivedFields);
2035
- usedFallbackFields = resolvedFields.length > 0;
2036
- }
2037
-
2038
- const trimmedFields = trimFieldsForPrompt(resolvedFields, fieldLimit, usedFallbackFields);
2039
- const arraySummary = buildArrayFieldSummary(trimmedFields.fields);
2040
-
2041
- const systemPrompt = buildReportBuilderSystemPrompt(reportType);
2042
- let userPrompt = buildReportBuilderUserPrompt({
2043
- prompt,
2044
- reportType,
2045
- reportName: normalizeOptionalString(input.report_name),
2046
- collectionRoot,
2047
- collectionJoins,
2048
- idDateField,
2049
- dateInterval,
2050
- collections,
2051
- fields: trimmedFields.fields,
2052
- fieldMeta: {
2053
- truncated: trimmedFields.truncated,
2054
- total: trimmedFields.total,
2055
- used: trimmedFields.fields.length
2056
- },
2057
- arraySummary
2058
- });
2059
-
2060
- const codexModel = resolveReportBuilderCodexModel(input.config);
2061
- const codexClient = getReportBuilderCodexClient();
2062
- const runOptions: CodexRunOptions = {
2063
- timeoutMs: resolveReportBuilderCodexTimeoutMs(input.config),
2064
- threadOptions: resolveReportBuilderCodexThreadOptions(input.config, codexModel)
2065
- };
2066
-
2067
- let responseText = await codexClient.run(buildReportBuilderCodexPrompt(systemPrompt, userPrompt), runOptions);
2068
- let usage = estimateUsage(
2069
- [
2070
- { role: 'system', content: systemPrompt },
2071
- { role: 'user', content: userPrompt }
2072
- ],
2073
- responseText,
2074
- codexModel
2075
- );
2076
-
2077
- let parsed: any = safeJsonParse(responseText);
2078
- const allowMongoRead = input.allow_mongo_read === true;
2079
- if (allowMongoRead && parsed && typeof parsed === 'object' && parsed.mongo_read) {
2080
- const mongoPayload = resolveMongoReadPayload(parsed.mongo_read, input, collections);
2081
- if (mongoPayload) {
2082
- let mongoReadResult: any = null;
2083
- try {
2084
- mongoReadResult = await executeAiAssistantMongoRead(mongoPayload, context);
2085
- }
2086
- catch (error) {
2087
- const message = error instanceof Error ? error.message : String(error || 'Mongo read failed.');
2088
- mongoReadResult = { error: message };
2089
- }
2090
- const mongoContext = buildMongoReadContext(mongoPayload, mongoReadResult);
2091
- userPrompt = buildReportBuilderUserPrompt({
2092
- prompt,
2093
- reportType,
2094
- reportName: normalizeOptionalString(input.report_name),
2095
- collectionRoot,
2096
- collectionJoins,
2097
- idDateField,
2098
- dateInterval,
2099
- collections,
2100
- fields: trimmedFields.fields,
2101
- fieldMeta: {
2102
- truncated: trimmedFields.truncated,
2103
- total: trimmedFields.total,
2104
- used: trimmedFields.fields.length
2105
- },
2106
- arraySummary,
2107
- mongoReadResult: mongoContext
2108
- });
2109
- responseText = await codexClient.run(buildReportBuilderCodexPrompt(systemPrompt, userPrompt), runOptions);
2110
- const usage2 = estimateUsage(
2111
- [
2112
- { role: 'system', content: systemPrompt },
2113
- { role: 'user', content: userPrompt }
2114
- ],
2115
- responseText,
2116
- codexModel
2117
- );
2118
- usage = {
2119
- inputTokens: usage.inputTokens + usage2.inputTokens,
2120
- outputTokens: usage.outputTokens + usage2.outputTokens,
2121
- totalTokens: usage.totalTokens + usage2.totalTokens
2122
- };
2123
- parsed = safeJsonParse(responseText);
2124
- }
2125
- }
2126
-
2127
- if (!parsed || typeof parsed !== 'object') {
2128
- throw new Error('AI response was not valid JSON.');
2129
- }
2130
-
2131
- const sanitizeContext = buildSanitizerContext(collections, resolvedFields);
2132
- const patch = sanitizeReportBuilderPatch(parsed, sanitizeContext);
2133
- patch.report_type = reportType;
2134
- if (reportType === 'Dated' && !patch.date_interval && dateInterval) {
2135
- patch.date_interval = dateInterval;
2136
- }
2137
-
2138
- const summaryNotes = buildPatchNotes(patch, {
2139
- reportType,
2140
- truncated: trimmedFields.truncated,
2141
- totalFields: trimmedFields.total,
2142
- usedFields: trimmedFields.fields.length,
2143
- inferenceReason: reportTypeResolution.inferenceReason
2144
- });
2145
-
2146
- const idClient = await resolveClientId(input.id_client, context?.id_user);
2147
- await recordOpenAIUsage({
2148
- id_client: idClient || '',
2149
- model: codexModel || 'unknown',
2150
- input_tokens: usage.inputTokens,
2151
- output_tokens: usage.outputTokens,
2152
- total_tokens: usage.totalTokens,
2153
- category: 'report-builder-ai',
2154
- id_request: ''
2155
- });
2156
-
2157
- return {
2158
- patch,
2159
- notes: summaryNotes,
2160
- usage: {
2161
- input_tokens: usage.inputTokens,
2162
- output_tokens: usage.outputTokens,
2163
- total_tokens: usage.totalTokens
2164
- },
2165
- model: codexModel
2166
- };
2167
- }
2168
-
2169
- function buildReportBuilderSystemPrompt(reportType: string): string {
2170
- return [
2171
- 'You are a report builder configuration assistant.',
2172
- 'Return ONLY a JSON object (no markdown).',
2173
- 'Schema keys:',
2174
- '- report_name (string)',
2175
- '- collection_root (string)',
2176
- '- collection_joins (array of { collection, alias })',
2177
- '- layout_columns (array of { header, field_path, collection, mode, text, leaf_value_type, leaf_format_type, show })',
2178
- '- groups_row (array of field_path strings)',
2179
- '- filters (array of { field_path, condition, value, high_value, boolean_value, is_rolling, rolling_interval })',
2180
- '- sort (array of { field_path, order })',
2181
- '- totals (array of { type, fields })',
2182
- '- links (array of { field_first, field_second })',
2183
- '- id_date_field (string)',
2184
- '- date_interval (Seconds|Minutes|Hours|Daily|Weekly|Monthly|Quarterly|Yearly)',
2185
- '- notes (string)',
2186
- 'Use only provided collections and field_path values (they reflect the current models/collections).',
2187
- 'Always set collection_root to the best matching collection from available_collections.',
2188
- 'When available_fields spans multiple collections, choose collection_root first and then select fields whose collection_name matches collection_root or a join alias.',
2189
- 'Prefer exact field_path values from available_fields; never invent field paths.',
2190
- 'Translate the request into concrete layout_columns, groups_row, sort, totals, filters, and links whenever possible.',
2191
- 'Every layout column must be complete: include a header plus at least one mapping with a valid field_path (or text mode with non-empty text). Do not return columns without mapped fields.',
2192
- 'Unless the user explicitly asks for a different order, add sort entries for the primary grouping/identifier fields (groups_row or the first key layout_columns) using ascending order so results are alphabetical.',
2193
- 'Always prioritize sort on popular business keys when available: name, company, item (fallback: title, label, customer). Keep ascending unless user asks otherwise.',
2194
- 'If no popular key exists, add stable fallback sort using createdAt/date_created then _id.',
2195
- 'Use filters with explicit boolean logic: $and for required constraints and $or for alternate matches.',
2196
- 'Use custom fields when the request asks for derived metrics (ratios, percentages, margins, computed totals).',
2197
- 'If the request specifies a time grain (daily/weekly/monthly/etc), set date_interval and id_date_field to the best matching date field.',
2198
- 'Treat requests like "over the last X", "by month", "by quarter", and "by <time period>" as Dated report intent.',
2199
- 'If you need sample records to confirm field choices, you may return JSON with {"mongo_read": { "collection": "<collection>", "query": {}, "options": {"limit": 5} }, "notes": "why you need data"} .',
2200
- 'Only request mongo_read when necessary and only use collections from available_collections.',
2201
- 'When mongo_read_result is present in the user context, use it to finalize the patch and do not request another read.',
2202
- 'Use field_type to guide filters/totals (numeric fields for totals, date fields for date_interval, boolean fields for boolean_value).',
2203
- 'If field_summary.truncated is true, only use provided fields and note missing needs in notes.',
2204
- 'If you need joined data, add collection_joins with an alias and use the alias in field_path.',
2205
- 'When fields require related records, use lookup paths and joins rather than guessing flat field names.',
2206
- 'array_summary lists array roots and candidate key_fields for linking arrays.',
2207
- 'If you select fields from multiple array roots, add links that align arrays using shared identifiers (_id or id_<x> style keys).',
2208
- 'Avoid mixing unrelated array roots without links; prefer a single array root or non-array fields to prevent duplicate rows.',
2209
- 'Return a single valid JSON object with double quotes and no trailing commas.',
2210
- 'Omit keys you cannot confidently fill.',
2211
- 'Conditions allowed: eq, ne, lt, lte, gt, gte, bw, nnull, null, regex.',
2212
- `Report type: ${reportType || 'List'}. Ensure groups_row is set for Group/Dated reports.`,
2213
- 'If unsure, leave a section empty instead of guessing.'
2214
- ].join('\n');
2215
- }
2216
-
2217
- function buildReportBuilderUserPrompt(input: {
2218
- prompt: string;
2219
- reportType: string;
2220
- reportName: string;
2221
- collectionRoot: string;
2222
- collectionJoins: Array<{ collection?: string; alias?: string }>;
2223
- idDateField: string;
2224
- dateInterval: string;
2225
- collections: Array<{ collection: string; name: string }>;
2226
- fields: Array<{ field_path: string; field_path_name: string; field_type: string; collection_name: string }>;
2227
- fieldMeta?: { truncated: boolean; total: number; used: number };
2228
- arraySummary?: Array<{ root_path: string; key_fields: string[]; sample_fields: string[] }>;
2229
- mongoReadResult?: any;
2230
- }): string {
2231
- const context = {
2232
- report_type: input.reportType || 'List',
2233
- current: {
2234
- report_name: input.reportName || '',
2235
- collection_root: input.collectionRoot || '',
2236
- collection_joins: input.collectionJoins || [],
2237
- id_date_field: input.idDateField || '',
2238
- date_interval: input.dateInterval || ''
2239
- },
2240
- available_collections: input.collections || [],
2241
- available_fields: input.fields || [],
2242
- field_summary: input.fieldMeta || undefined,
2243
- array_summary: input.arraySummary || [],
2244
- mongo_read_result: input.mongoReadResult || undefined,
2245
- request: input.prompt
2246
- };
2247
- return JSON.stringify(context);
2248
- }
2249
-
2250
- function resolveReportBuilderCodexTimeoutMs(config?: any): number {
2251
- const serverConfig = ResolveIOServer.getServerConfig() || {};
2252
- const raw = normalizeOptionalNumber(
2253
- config?.timeout_ms
2254
- || serverConfig['REPORT_BUILDER_CODEX_TIMEOUT_MS']
2255
- || process.env.REPORT_BUILDER_CODEX_TIMEOUT_MS
2256
- );
2257
- if (raw && raw > 0) {
2258
- return round(raw);
2259
- }
2260
- return DEFAULT_REPORT_BUILDER_CODEX_TIMEOUT_MS;
2261
- }
2262
-
2263
- function resolveReportBuilderCodexModel(config?: any): string {
2264
- const serverConfig = ResolveIOServer.getServerConfig() || {};
2265
- const overrideModel = normalizeOptionalString(config?.model);
2266
- const raw = overrideModel
2267
- || normalizeOptionalString(serverConfig['REPORT_BUILDER_CODEX_MODEL'] || process.env.REPORT_BUILDER_CODEX_MODEL)
2268
- || normalizeOptionalString(serverConfig['AI_DASHBOARD_CODEX_MODEL'] || process.env.AI_DASHBOARD_CODEX_MODEL)
2269
- || normalizeOptionalString(serverConfig['AI_TERMINAL_CODEX_MODEL'] || process.env.AI_TERMINAL_CODEX_MODEL)
2270
- || normalizeOptionalString(serverConfig['AI_ASSISTANT_CODEX_MODEL'] || process.env.AI_ASSISTANT_CODEX_MODEL);
2271
- return raw;
2272
- }
2273
-
2274
- function resolveReportBuilderCodexFallbackModels(model?: string): string[] {
2275
- const serverConfig = ResolveIOServer.getServerConfig() || {};
2276
- const primary = normalizeOptionalString(model || resolveReportBuilderCodexModel());
2277
- const models: string[] = [];
2278
- const push = (value: any) => {
2279
- const normalized = normalizeOptionalString(value);
2280
- if (!normalized || normalized === primary || models.includes(normalized)) {
2281
- return;
2282
- }
2283
- models.push(normalized);
2284
- };
2285
- const parseList = (value: any) => normalizeOptionalString(value)
2286
- .split(',')
2287
- .map(entry => normalizeOptionalString(entry))
2288
- .filter(Boolean)
2289
- .forEach(push);
2290
-
2291
- parseList(serverConfig['REPORT_BUILDER_CODEX_FALLBACK_MODELS'] || process.env.REPORT_BUILDER_CODEX_FALLBACK_MODELS);
2292
- push(serverConfig['REPORT_BUILDER_CODEX_FALLBACK_MODEL'] || process.env.REPORT_BUILDER_CODEX_FALLBACK_MODEL);
2293
- parseList(serverConfig['AI_DASHBOARD_CODEX_FALLBACK_MODELS'] || process.env.AI_DASHBOARD_CODEX_FALLBACK_MODELS);
2294
- push(serverConfig['AI_DASHBOARD_CODEX_FALLBACK_MODEL'] || process.env.AI_DASHBOARD_CODEX_FALLBACK_MODEL);
2295
- parseList(serverConfig['AI_TERMINAL_CODEX_FALLBACK_MODELS'] || process.env.AI_TERMINAL_CODEX_FALLBACK_MODELS);
2296
- push(serverConfig['AI_TERMINAL_CODEX_FALLBACK_MODEL'] || process.env.AI_TERMINAL_CODEX_FALLBACK_MODEL);
2297
- parseList(serverConfig['AI_ASSISTANT_CODEX_FALLBACK_MODELS'] || process.env.AI_ASSISTANT_CODEX_FALLBACK_MODELS);
2298
- push(serverConfig['AI_ASSISTANT_CODEX_FALLBACK_MODEL'] || process.env.AI_ASSISTANT_CODEX_FALLBACK_MODEL);
2299
- return models;
2300
- }
2301
-
2302
- function resolveReportBuilderCodexSettings(): CodexConfig {
2303
- const serverConfig = ResolveIOServer.getServerConfig() || {};
2304
- const apiKey = (serverConfig['OPENAI_API_KEY'] || process.env.OPENAI_API_KEY || '').trim();
2305
- if (!apiKey) {
2306
- throw new Error('AI API key missing. Add an AI API key to server config.');
2307
- }
2308
- const model = resolveReportBuilderCodexModel();
2309
- const fallbackModels = resolveReportBuilderCodexFallbackModels(model);
2310
- return {
2311
- apiKey,
2312
- baseUrl: (serverConfig['OPENAI_BASE_URL'] || process.env.OPENAI_BASE_URL || '').trim() || undefined,
2313
- ...(model ? { model } : {}),
2314
- ...(fallbackModels.length ? { fallbackModel: fallbackModels[0], fallbackModels } : {}),
2315
- maxRetries: normalizeOptionalNumber(serverConfig['OPENAI_MAX_RETRIES'] || process.env.OPENAI_MAX_RETRIES),
2316
- retryDelayMs: normalizeOptionalNumber(serverConfig['OPENAI_RETRY_DELAY_MS'] || process.env.OPENAI_RETRY_DELAY_MS)
2317
- };
2318
- }
2319
-
2320
- function resolveReportBuilderCodexThreadOptions(config?: any, model?: string): CodexThreadOptions {
2321
- const serverConfig = ResolveIOServer.getServerConfig() || {};
2322
- const effort = normalizeReasoningEffort(
2323
- config?.reasoning_effort
2324
- || serverConfig['REPORT_BUILDER_CODEX_REASONING_EFFORT']
2325
- || process.env.REPORT_BUILDER_CODEX_REASONING_EFFORT
2326
- ) || 'low';
2327
- return {
2328
- ...(model ? { model } : {}),
2329
- sandboxMode: 'read-only',
2330
- skipGitRepoCheck: true,
2331
- modelReasoningEffort: effort,
2332
- networkAccessEnabled: false,
2333
- webSearchMode: 'disabled',
2334
- webSearchEnabled: false,
2335
- approvalPolicy: 'never'
2336
- };
2337
- }
2338
-
2339
- function getReportBuilderCodexClient(): CodexClient {
2340
- if (!reportBuilderCodexClient) {
2341
- reportBuilderCodexClient = new CodexClient(resolveReportBuilderCodexSettings());
2342
- }
2343
- return reportBuilderCodexClient;
2344
- }
2345
-
2346
- function buildReportBuilderCodexPrompt(systemPrompt: string, userPrompt: string): string {
2347
- return `System:\n${systemPrompt}\n\nUser:\n${userPrompt}`.trim();
2348
- }
2349
-
2350
- function resolveReportTypeForPrompt(inputType: string | undefined, prompt: string): ReportTypeResolution {
2351
- const requestedType = normalizeReportType(inputType);
2352
- const inferredInterval = inferDateIntervalFromPrompt(prompt);
2353
- if (!isDatedPrompt(prompt)) {
2354
- return {
2355
- reportType: requestedType,
2356
- inferenceReason: '',
2357
- inferredDateInterval: inferredInterval
2358
- };
2359
- }
2360
- const reason = requestedType !== 'Dated'
2361
- ? 'Auto-inferred Dated report from time-period language in the request.'
2362
- : '';
2363
- return {
2364
- reportType: 'Dated',
2365
- inferenceReason: reason,
2366
- inferredDateInterval: inferredInterval
2367
- };
2368
- }
2369
-
2370
- function isDatedPrompt(prompt: string): boolean {
2371
- const text = normalizeOptionalString(prompt).toLowerCase();
2372
- if (!text) {
2373
- return false;
2374
- }
2375
- const patterns = [
2376
- /\b(by|per)\s+(day|week|month|quarter|year|hour|minute|second)s?\b/i,
2377
- /\b(daily|weekly|monthly|quarterly|yearly)\b/i,
2378
- /\b(last|past|previous|recent)\s+(?:\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+(day|week|month|quarter|year|hour|minute|second)s?\b/i,
2379
- /\bover\s+the\s+last\s+(?:\d+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\s+(day|week|month|quarter|year|hour|minute|second)s?\b/i,
2380
- /\b(month|quarter|year)\s+over\s+(month|quarter|year)\b/i,
2381
- /\b(over\s+time|time\s*series|trend(?:ing|s)?)\b/i,
2382
- /\bby\s+(?:a\s+)?(?:time|date)\s+period\b/i,
2383
- /\bby\s+period\b/i
2384
- ];
2385
- return patterns.some(pattern => pattern.test(text));
2386
- }
2387
-
2388
- function inferDateIntervalFromPrompt(prompt: string): string {
2389
- const text = normalizeOptionalString(prompt).toLowerCase();
2390
- if (!text) {
2391
- return '';
2392
- }
2393
- const mapping: Array<{ interval: string; pattern: RegExp }> = [
2394
- { interval: 'Quarterly', pattern: /\b(quarter|quarters|quarterly|qoq)\b/i },
2395
- { interval: 'Monthly', pattern: /\b(month|months|monthly|mom)\b/i },
2396
- { interval: 'Weekly', pattern: /\b(week|weeks|weekly|wow)\b/i },
2397
- { interval: 'Daily', pattern: /\b(day|days|daily)\b/i },
2398
- { interval: 'Yearly', pattern: /\b(year|years|yearly|yoy)\b/i },
2399
- { interval: 'Hours', pattern: /\b(hour|hours|hourly)\b/i },
2400
- { interval: 'Minutes', pattern: /\b(minute|minutes)\b/i },
2401
- { interval: 'Seconds', pattern: /\b(second|seconds)\b/i }
2402
- ];
2403
- const matched = mapping.find(entry => entry.pattern.test(text));
2404
- return matched?.interval || '';
2405
- }
2406
-
2407
- function normalizeReportType(value?: string): string {
2408
- const normalized = normalizeOptionalString(value);
2409
- if (normalized.toLowerCase() === 'group') {
2410
- return 'Group';
2411
- }
2412
- if (normalized.toLowerCase() === 'dated') {
2413
- return 'Dated';
2414
- }
2415
- return normalized ? toTitleCase(normalized) : 'List';
2416
- }
2417
-
2418
- function normalizeOptionalString(value: any): string {
2419
- const raw = typeof value === 'string' ? value.trim() : '';
2420
- return raw || '';
2421
- }
2422
-
2423
- function normalizeOptionalNumber(value: any): number | undefined {
2424
- const parsed = Number(value);
2425
- return Number.isFinite(parsed) ? parsed : undefined;
2426
- }
2427
-
2428
- function normalizeReasoningEffort(value: any): CodexThreadOptions['modelReasoningEffort'] | undefined {
2429
- const normalized = normalizeOptionalString(value).toLowerCase();
2430
- const allowed = new Set(['minimal', 'low', 'medium', 'high', 'xhigh']);
2431
- return allowed.has(normalized) ? (normalized as CodexThreadOptions['modelReasoningEffort']) : undefined;
2432
- }
2433
-
2434
- function normalizeFieldLimit(value: any): number {
2435
- const parsed = Number(value);
2436
- if (!Number.isFinite(parsed)) {
2437
- return 600;
2438
- }
2439
- return Math.max(50, Math.min(round(parsed), 2000));
2440
- }
2441
-
2442
- function estimateUsage(messages: Array<{ role: string; content: string }>, responseText: string, model?: string) {
2443
- const inputTokens = countChatTokens(messages, model);
2444
- const outputTokens = countTokens(responseText || '', model);
2445
- return {
2446
- inputTokens,
2447
- outputTokens,
2448
- totalTokens: inputTokens + outputTokens
2449
- };
2450
- }
2451
-
2452
- function sanitizeCollections(raw: Array<{ collection?: string; name?: string }>): Array<{ collection: string; name: string }> {
2453
- const out: Array<{ collection: string; name: string }> = [];
2454
- const seen = new Set<string>();
2455
- (raw || []).forEach(entry => {
2456
- const collection = normalizeOptionalString(entry?.collection);
2457
- if (!collection) {
2458
- return;
2459
- }
2460
- const key = collection.toLowerCase();
2461
- if (seen.has(key)) {
2462
- return;
2463
- }
2464
- seen.add(key);
2465
- out.push({
2466
- collection,
2467
- name: normalizeOptionalString(entry?.name) || toTitleCase(collection.replace(/[-_]/g, ' '))
2468
- });
2469
- });
2470
- return out;
2471
- }
2472
-
2473
- function sanitizeFields(raw: Array<any>): Array<{ field_path: string; field_path_name: string; field_type: string; collection_name: string }> {
2474
- const out: Array<{ field_path: string; field_path_name: string; field_type: string; collection_name: string }> = [];
2475
- const seen = new Set<string>();
2476
- (raw || []).forEach(entry => {
2477
- const fieldPath = normalizeOptionalString(entry?.field_path || entry?.fieldPath);
2478
- if (!fieldPath) {
2479
- return;
2480
- }
2481
- const key = fieldPath.toLowerCase();
2482
- if (seen.has(key)) {
2483
- return;
2484
- }
2485
- seen.add(key);
2486
- out.push({
2487
- field_path: fieldPath,
2488
- field_path_name: normalizeOptionalString(entry?.field_path_name || entry?.fieldPathName || entry?.field_name) || fieldPath,
2489
- field_type: normalizeOptionalString(entry?.field_type || entry?.fieldType || entry?.field_type_name || entry?.fieldTypeName) || 'String',
2490
- collection_name: normalizeOptionalString(entry?.collection_name || entry?.collectionName || entry?.collection) || ''
2491
- });
2492
- });
2493
- return out;
2494
- }
2495
-
2496
- function resolveCollectionNameFromList(value: any, collections: Array<{ collection: string; name: string }>): string {
2497
- const key = normalizeKey(value);
2498
- if (!key) {
2499
- return '';
2500
- }
2501
- for (const col of collections || []) {
2502
- if (normalizeKey(col.collection) === key || normalizeKey(col.name) === key) {
2503
- return col.collection;
2504
- }
2505
- }
2506
- return '';
2507
- }
2508
-
2509
- function resolveMongoReadPayload(raw: any, input: ReportBuilderAiPayload, collections: Array<{ collection: string; name: string }>): AiAssistantMongoReadInput | null {
2510
- if (!raw || typeof raw !== 'object') {
2511
- return null;
2512
- }
2513
- const collection = resolveCollectionNameFromList(raw.collection || raw.collection_name || raw.collectionName, collections);
2514
- if (!collection) {
2515
- return null;
2516
- }
2517
- const query = raw.query && typeof raw.query === 'object' && !Array.isArray(raw.query) ? raw.query : {};
2518
- const optionsRaw = raw.options && typeof raw.options === 'object' ? raw.options : {};
2519
- const limitRaw = normalizeOptionalNumber(optionsRaw.limit);
2520
- const skipRaw = normalizeOptionalNumber(optionsRaw.skip);
2521
- const permissionView = normalizeOptionalString((input as any).permission_view || (input as any).permissionView) || '/report-builder';
2522
- const request: AiAssistantMongoReadInput = {
2523
- collection,
2524
- database: normalizeOptionalString(raw.database) || undefined,
2525
- query,
2526
- options: {
2527
- projection: optionsRaw.projection && typeof optionsRaw.projection === 'object' ? optionsRaw.projection : undefined,
2528
- sort: optionsRaw.sort && typeof optionsRaw.sort === 'object' ? optionsRaw.sort : undefined,
2529
- limit: limitRaw ? Math.min(Math.max(round(limitRaw), 1), 10) : 5,
2530
- skip: skipRaw ? Math.max(round(skipRaw), 0) : undefined,
2531
- includeTotal: optionsRaw.includeTotal === true
2532
- },
2533
- permissionView: permissionView || undefined,
2534
- id_client: normalizeOptionalString(input.id_client) || undefined,
2535
- mongo: input.mongo
2536
- };
2537
- return request;
2538
- }
2539
-
2540
- function buildMongoReadContext(request: AiAssistantMongoReadInput, response: any) {
2541
- const documents = Array.isArray(response?.documents) ? response.documents : [];
2542
- const total = typeof response?.total === 'number' ? response.total : null;
2543
- const sample = summarizeMongoDocuments(documents);
2544
- return {
2545
- request: {
2546
- collection: request.collection,
2547
- query: request.query || {},
2548
- options: request.options || {}
2549
- },
2550
- result: {
2551
- total,
2552
- sample,
2553
- fields: extractMongoSampleFields(sample)
2554
- }
2555
- };
2556
- }
2557
-
2558
- function summarizeMongoDocuments(documents: any[]) {
2559
- const maxDocs = 5;
2560
- return (documents || []).slice(0, maxDocs).map(doc => pruneMongoValue(doc, 0));
2561
- }
2562
-
2563
- function pruneMongoValue(value: any, depth: number): any {
2564
- const maxDepth = 3;
2565
- const maxKeys = 30;
2566
- const maxArray = 8;
2567
- const maxString = 200;
2568
-
2569
- if (depth >= maxDepth) {
2570
- return '[Truncated]';
2571
- }
2572
- if (Array.isArray(value)) {
2573
- return value.slice(0, maxArray).map(item => pruneMongoValue(item, depth + 1));
2574
- }
2575
- if (value && typeof value === 'object') {
2576
- const keys = Object.keys(value);
2577
- const trimmedKeys = keys.slice(0, maxKeys);
2578
- const result: any = {};
2579
- trimmedKeys.forEach(key => {
2580
- result[key] = pruneMongoValue(value[key], depth + 1);
2581
- });
2582
- if (keys.length > trimmedKeys.length) {
2583
- result.__truncated__ = true;
2584
- }
2585
- return result;
2586
- }
2587
- if (typeof value === 'string' && value.length > maxString) {
2588
- return value.slice(0, maxString) + '...';
2589
- }
2590
- return value;
2591
- }
2592
-
2593
- function extractMongoSampleFields(samples: any[]): string[] {
2594
- const fields = new Set<string>();
2595
- (samples || []).forEach(sample => {
2596
- if (!sample || typeof sample !== 'object') {
2597
- return;
2598
- }
2599
- Object.keys(sample).forEach(key => fields.add(key));
2600
- });
2601
- return Array.from(fields);
2602
- }
2603
-
2604
- function collectTreeLeaves(items: any[], res: any[]) {
2605
- (items || []).forEach(item => {
2606
- if (item?.isLeaf) {
2607
- res.push(item);
2608
- return;
2609
- }
2610
- if (item?.children?.length) {
2611
- collectTreeLeaves(item.children, res);
2612
- }
2613
- });
2614
- }
2615
-
2616
- function deriveFieldsFromCollections(collections: Array<{ collection: string; name: string }>) {
2617
- const fields: Array<{ field_path: string; field_path_name: string; field_type: string; collection_name: string }> = [];
2618
- const seen = new Set<string>();
2619
-
2620
- (collections || []).forEach(col => {
2621
- const collectionName = normalizeOptionalString(col?.collection);
2622
- if (!collectionName) {
2623
- return;
2624
- }
2625
-
2626
- let tree: any;
2627
- try {
2628
- tree = buildCollectionTree(collectionName);
2629
- }
2630
- catch (error) {
2631
- const message = error instanceof Error ? error.message : String(error || 'Unknown error');
2632
- console.warn('RB AI field derivation failed', {collection: collectionName, error: message});
2633
- return;
2634
- }
2635
-
2636
- const leaves: any[] = [];
2637
- collectTreeLeaves(tree?.children || [], leaves);
2638
-
2639
- leaves.forEach(leaf => {
2640
- const fieldPath = normalizeOptionalString(leaf?.fieldPath);
2641
- if (!fieldPath) {
2642
- return;
2643
- }
2644
- const key = fieldPath.toLowerCase();
2645
- if (seen.has(key)) {
2646
- return;
2647
- }
2648
- seen.add(key);
2649
- fields.push({
2650
- field_path: fieldPath,
2651
- field_path_name: normalizeOptionalString(leaf?.fieldPathName) || fieldPath,
2652
- field_type: normalizeOptionalString(leaf?.fieldType) || 'String',
2653
- collection_name: normalizeOptionalString(leaf?.collection_name || leaf?.collection) || collectionName
2654
- });
2655
- });
2656
- });
2657
-
2658
- return fields;
2659
- }
2660
-
2661
- function trimFieldsForPrompt(
2662
- fields: Array<{ field_path: string; field_path_name: string; field_type: string; collection_name: string }>,
2663
- limit: number,
2664
- balanceByCollection = false
2665
- ) {
2666
- if (!fields || !fields.length) {
2667
- return { fields: [], truncated: false, total: 0 };
2668
- }
2669
- if (fields.length <= limit) {
2670
- return { fields, truncated: false, total: fields.length };
2671
- }
2672
-
2673
- if (balanceByCollection) {
2674
- const buckets = new Map<string, Array<{ field_path: string; field_path_name: string; field_type: string; collection_name: string }>>();
2675
- const order: string[] = [];
2676
-
2677
- fields.forEach(field => {
2678
- const key = normalizeOptionalString(field?.collection_name) || 'unknown';
2679
- if (!buckets.has(key)) {
2680
- buckets.set(key, []);
2681
- order.push(key);
2682
- }
2683
- buckets.get(key)?.push(field);
2684
- });
2685
-
2686
- if (buckets.size > 1) {
2687
- const trackers = order.map(key => ({
2688
- key,
2689
- list: buckets.get(key) || [],
2690
- index: 0
2691
- }));
2692
- const trimmed: Array<{ field_path: string; field_path_name: string; field_type: string; collection_name: string }> = [];
2693
- let added = true;
2694
-
2695
- while (trimmed.length < limit && added) {
2696
- added = false;
2697
- for (const tracker of trackers) {
2698
- if (trimmed.length >= limit) {
2699
- break;
2700
- }
2701
- if (tracker.index < tracker.list.length) {
2702
- trimmed.push(tracker.list[tracker.index]);
2703
- tracker.index += 1;
2704
- added = true;
2705
- }
2706
- }
2707
- }
2708
-
2709
- return { fields: trimmed, truncated: true, total: fields.length };
2710
- }
2711
- }
2712
-
2713
- const trimmed = fields.slice(0, limit);
2714
- return { fields: trimmed, truncated: true, total: fields.length };
2715
- }
2716
-
2717
- function buildArrayFieldSummary(
2718
- fields: Array<{ field_path: string; field_path_name: string; field_type: string; collection_name: string }>
2719
- ): Array<{ root_path: string; key_fields: string[]; sample_fields: string[] }> {
2720
- const roots = new Map<string, { root_path: string; key_fields: string[]; sample_fields: string[] }>();
2721
- (fields || []).forEach(field => {
2722
- const fieldPath = normalizeOptionalString(field?.field_path);
2723
- if (!fieldPath || !fieldPath.includes('.$')) {
2724
- return;
2725
- }
2726
- const root = resolveArrayRoot(fieldPath);
2727
- if (!root) {
2728
- return;
2729
- }
2730
- let entry = roots.get(root);
2731
- if (!entry) {
2732
- entry = { root_path: root, key_fields: [], sample_fields: [] };
2733
- roots.set(root, entry);
2734
- }
2735
- if (!entry.sample_fields.includes(fieldPath) && entry.sample_fields.length < 8) {
2736
- entry.sample_fields.push(fieldPath);
2737
- }
2738
- if (isLinkCandidateFieldPath(fieldPath) && !entry.key_fields.includes(fieldPath) && entry.key_fields.length < 8) {
2739
- entry.key_fields.push(fieldPath);
2740
- }
2741
- });
2742
- return Array.from(roots.values());
2743
- }
2744
-
2745
- function buildSanitizerContext(
2746
- collections: Array<{ collection: string; name: string }>,
2747
- fields: Array<{ field_path: string; field_path_name: string; field_type: string; collection_name: string }>
2748
- ) {
2749
- const collectionIndex = new Map<string, { collection: string; name: string }>();
2750
- const fieldIndex = new Map<string, { field_path: string; field_path_name: string; field_type: string; collection_name: string }>();
2751
- const fieldNameIndex = new Map<string, string>();
2752
-
2753
- collections.forEach(col => {
2754
- const key = normalizeKey(col.collection);
2755
- if (key) {
2756
- collectionIndex.set(key, col);
2757
- }
2758
- const nameKey = normalizeKey(col.name);
2759
- if (nameKey) {
2760
- collectionIndex.set(nameKey, col);
2761
- }
2762
- });
2763
-
2764
- fields.forEach(field => {
2765
- const pathKey = normalizePathKey(field.field_path);
2766
- if (pathKey) {
2767
- fieldIndex.set(pathKey, field);
2768
- }
2769
- const nameKey = normalizeKey(field.field_path_name);
2770
- if (nameKey && !fieldNameIndex.has(nameKey)) {
2771
- fieldNameIndex.set(nameKey, field.field_path);
2772
- }
2773
- });
2774
-
2775
- return { collectionIndex, fieldIndex, fieldNameIndex };
2776
- }
2777
-
2778
- function sanitizeReportBuilderPatch(raw: any, ctx: ReturnType<typeof buildSanitizerContext>) {
2779
- const patch: any = {};
2780
- if (!raw || typeof raw !== 'object') {
2781
- return patch;
2782
- }
2783
-
2784
- const reportName = normalizeOptionalString(raw.report_name || raw.reportName || raw.title);
2785
- if (reportName) {
2786
- patch.report_name = reportName.slice(0, 120);
2787
- }
2788
-
2789
- const collectionRoot = resolveCollectionName(raw.collection_root || raw.collectionRoot, ctx);
2790
- if (collectionRoot) {
2791
- patch.collection_root = collectionRoot;
2792
- }
2793
-
2794
- const joinsRaw = Array.isArray(raw.collection_joins || raw.joins) ? (raw.collection_joins || raw.joins) : [];
2795
- const joins: Array<{ collection: string; alias?: string }> = [];
2796
- joinsRaw.forEach((join: any) => {
2797
- const collection = resolveCollectionName(join?.collection || join?.name, ctx);
2798
- if (!collection) {
2799
- return;
2800
- }
2801
- const alias = sanitizeAlias(join?.alias);
2802
- joins.push({ collection, ...(alias ? { alias } : {}) });
2803
- });
2804
- if (joins.length) {
2805
- patch.collection_joins = joins;
2806
- }
2807
-
2808
- const layoutColumnsRaw = Array.isArray(raw.layout_columns || raw.columns || raw.layout) ? (raw.layout_columns || raw.columns || raw.layout) : [];
2809
- const layoutColumns: any[] = [];
2810
- layoutColumnsRaw.forEach((col: any) => {
2811
- if (!col || typeof col !== 'object') {
2812
- return;
2813
- }
2814
- let header = normalizeOptionalString(col.header || col.title || col.name);
2815
- let fieldPath = resolveFieldPath(col.field_path || col.fieldPath || col.field || col.field_name || col.fieldName || col.path, ctx);
2816
- if (!fieldPath && header) {
2817
- fieldPath = resolveFieldPath(header, ctx);
2818
- }
2819
- const mappingsRaw = Array.isArray(col.mappings) ? col.mappings : [];
2820
- const mappings: any[] = [];
2821
- mappingsRaw.forEach((map: any) => {
2822
- const mappedField = resolveFieldPath(map?.field_path || map?.fieldPath || map?.field || map?.field_name || map?.fieldName || map?.path || map?.name, ctx);
2823
- const mode = normalizeLayoutMode(map?.mode);
2824
- const text = normalizeOptionalString(map?.text);
2825
- const collection = resolveCollectionName(map?.collection, ctx);
2826
- if (mode === 'text') {
2827
- if (text) {
2828
- mappings.push({
2829
- mode,
2830
- text,
2831
- collection
2832
- });
2833
- }
2834
- }
2835
- else if (mappedField) {
2836
- mappings.push({
2837
- mode,
2838
- field_path: mappedField,
2839
- collection,
2840
- leaf_value_type: normalizeOptionalString(map?.leaf_value_type || map?.leafValueType),
2841
- leaf_format_type: normalizeOptionalString(map?.leaf_format_type || map?.leafFormatType),
2842
- show: typeof map?.show === 'boolean' ? map.show : undefined
2843
- });
2844
- }
2845
- });
2846
-
2847
- if (!mappings.length) {
2848
- if (fieldPath) {
2849
- mappings.push({ field_path: fieldPath, mode: normalizeLayoutMode(col.mode) });
2850
- }
2851
- else {
2852
- const text = normalizeOptionalString(col.text);
2853
- const mode = normalizeLayoutMode(col.mode);
2854
- if ((mode === 'text' || text) && text) {
2855
- mappings.push({
2856
- mode: 'text',
2857
- text,
2858
- collection: resolveCollectionName(col.collection, ctx)
2859
- });
2860
- }
2861
- }
2862
- }
2863
-
2864
- if (!fieldPath) {
2865
- const firstMapped = mappings.find(mapping => !!mapping?.field_path);
2866
- if (firstMapped?.field_path) {
2867
- fieldPath = firstMapped.field_path;
2868
- }
2869
- }
2870
-
2871
- if (!header && fieldPath) {
2872
- const meta = ctx.fieldIndex.get(normalizePathKey(fieldPath));
2873
- header = normalizeOptionalString(meta?.field_path_name);
2874
- }
2875
-
2876
- if (!mappings.length) {
2877
- return;
2878
- }
2879
-
2880
- layoutColumns.push({
2881
- header: header || '',
2882
- field_path: fieldPath || '',
2883
- collection: resolveCollectionName(col.collection, ctx),
2884
- mode: normalizeLayoutMode(col.mode),
2885
- text: normalizeOptionalString(col.text),
2886
- mappings
2887
- });
2888
- });
2889
-
2890
- if (!layoutColumns.length && Array.isArray(raw.selected_fields)) {
2891
- (raw.selected_fields || []).forEach((field: any) => {
2892
- const fieldPath = resolveFieldPath(field, ctx);
2893
- if (!fieldPath) {
2894
- return;
2895
- }
2896
- layoutColumns.push({
2897
- header: '',
2898
- field_path: fieldPath,
2899
- mappings: [{ field_path: fieldPath }]
2900
- });
2901
- });
2902
- }
2903
-
2904
- if (layoutColumns.length) {
2905
- patch.layout_columns = layoutColumns;
2906
- }
2907
-
2908
- const groupsRowRaw = Array.isArray(raw.groups_row) ? raw.groups_row : [];
2909
- const groupsRow = groupsRowRaw.map((entry: any) => resolveFieldPath(entry, ctx)).filter(Boolean);
2910
- if (groupsRow.length) {
2911
- patch.groups_row = groupsRow;
2912
- }
2913
-
2914
- const filtersRaw = Array.isArray(raw.filters) ? raw.filters : [];
2915
- const filters: any[] = [];
2916
- filtersRaw.forEach((filter: any) => {
2917
- const fieldPath = resolveFieldPath(filter?.field_path || filter?.fieldPath, ctx);
2918
- if (!fieldPath) {
2919
- return;
2920
- }
2921
- const fieldMeta = ctx.fieldIndex.get(normalizePathKey(fieldPath));
2922
- const condition = normalizeFilterCondition(filter?.condition);
2923
- if (!condition) {
2924
- return;
2925
- }
2926
- const value = normalizeFilterValue(filter?.value, fieldMeta?.field_type);
2927
- const highValue = normalizeFilterValue(filter?.high_value, fieldMeta?.field_type);
2928
- const booleanValue = normalizeBooleanValue(filter?.boolean_value);
2929
- filters.push({
2930
- field_path: fieldPath,
2931
- condition,
2932
- value,
2933
- high_value: highValue,
2934
- boolean_value: booleanValue,
2935
- is_rolling: typeof filter?.is_rolling === 'boolean' ? filter.is_rolling : undefined,
2936
- rolling_interval: normalizeOptionalString(filter?.rolling_interval)
2937
- });
2938
- });
2939
- if (filters.length) {
2940
- patch.filters = filters;
2941
- }
2942
-
2943
- const sortRaw = Array.isArray(raw.sort) ? raw.sort : [];
2944
- const sort: any[] = [];
2945
- sortRaw.forEach((entry: any) => {
2946
- const fieldPath = resolveFieldPath(entry?.field_path || entry?.fieldPath, ctx);
2947
- if (!fieldPath) {
2948
- return;
2949
- }
2950
- sort.push({
2951
- field_path: fieldPath,
2952
- order: normalizeSortOrder(entry?.order)
2953
- });
2954
- });
2955
- if (sort.length) {
2956
- patch.sort = sort;
2957
- }
2958
- else {
2959
- const defaultSortFields = buildDefaultSortFields(groupsRow, layoutColumns);
2960
- if (defaultSortFields.length) {
2961
- patch.sort = defaultSortFields.map(field_path => ({ field_path, order: 'asc' }));
2962
- }
2963
- }
2964
-
2965
- const totalsRaw = Array.isArray(raw.totals) ? raw.totals : [];
2966
- const totals: any[] = [];
2967
- totalsRaw.forEach((total: any) => {
2968
- const type = normalizeTotalType(total?.type);
2969
- const fields = Array.isArray(total?.fields) ? total.fields : [];
2970
- const mappedFields = fields.map((field: any) => resolveFieldPath(field, ctx)).filter(Boolean);
2971
- if (!type || !mappedFields.length) {
2972
- return;
2973
- }
2974
- totals.push({ type, fields: mappedFields });
2975
- });
2976
- if (totals.length) {
2977
- patch.totals = totals;
2978
- }
2979
-
2980
- const linksRaw = Array.isArray(raw.links) ? raw.links : [];
2981
- const links: any[] = [];
2982
- linksRaw.forEach((link: any) => {
2983
- const first = resolveFieldPath(link?.field_first || link?.fieldFirst, ctx);
2984
- const second = resolveFieldPath(link?.field_second || link?.fieldSecond, ctx);
2985
- if (!first || !second) {
2986
- return;
2987
- }
2988
- links.push({ field_first: first, field_second: second });
2989
- });
2990
- if (links.length) {
2991
- patch.links = links;
2992
- }
2993
-
2994
- const dateField = resolveFieldPath(raw.id_date_field || raw.date_field || raw.dateField, ctx);
2995
- if (dateField) {
2996
- patch.id_date_field = dateField;
2997
- }
2998
-
2999
- const dateInterval = normalizeDateInterval(raw.date_interval || raw.dateInterval);
3000
- if (dateInterval) {
3001
- patch.date_interval = dateInterval;
3002
- }
3003
-
3004
- const notes = normalizeOptionalString(raw.notes || raw.summary || raw.message);
3005
- if (notes) {
3006
- patch.notes = notes.slice(0, 400);
3007
- }
3008
-
3009
- return patch;
3010
- }
3011
-
3012
- function buildDefaultSortFields(groupsRow: string[], layoutColumns: any[]): string[] {
3013
- const fields: string[] = [];
3014
- const addField = (fieldPath?: string) => {
3015
- const normalized = normalizeOptionalString(fieldPath);
3016
- if (normalized && !fields.includes(normalized)) {
3017
- fields.push(normalized);
3018
- }
3019
- };
3020
-
3021
- (groupsRow || []).forEach(addField);
3022
- if (fields.length) {
3023
- return fields;
3024
- }
3025
-
3026
- (layoutColumns || []).forEach((col: any) => {
3027
- if (!col || typeof col !== 'object') {
3028
- return;
3029
- }
3030
- let fieldPath = '';
3031
- const mappings = Array.isArray(col.mappings) ? col.mappings : [];
3032
- for (const mapping of mappings) {
3033
- if (mapping?.field_path) {
3034
- fieldPath = mapping.field_path;
3035
- break;
3036
- }
3037
- }
3038
- if (!fieldPath && col.field_path) {
3039
- fieldPath = col.field_path;
3040
- }
3041
- addField(fieldPath);
3042
- });
3043
-
3044
- return fields;
3045
- }
3046
-
3047
- function buildPatchNotes(
3048
- patch: any,
3049
- meta: { reportType: string; truncated: boolean; totalFields: number; usedFields: number; inferenceReason?: string }
3050
- ) {
3051
- const columns = Array.isArray(patch?.layout_columns) ? patch.layout_columns.length : 0;
3052
- const groups = Array.isArray(patch?.groups_row) ? patch.groups_row.length : 0;
3053
- const filters = Array.isArray(patch?.filters) ? patch.filters.length : 0;
3054
- const joins = Array.isArray(patch?.collection_joins) ? patch.collection_joins.length : 0;
3055
- const parts = [
3056
- `AI suggested ${columns} column${columns === 1 ? '' : 's'}`,
3057
- groups ? `${groups} group${groups === 1 ? '' : 's'}` : '',
3058
- filters ? `${filters} filter${filters === 1 ? '' : 's'}` : '',
3059
- joins ? `${joins} join${joins === 1 ? '' : 's'}` : ''
3060
- ].filter(Boolean);
3061
- let note = parts.length ? parts.join(', ') + '.' : 'AI suggestions ready.';
3062
- if (patch?.notes) {
3063
- note += ` ${patch.notes}`;
3064
- }
3065
- if (meta.inferenceReason) {
3066
- note += ` ${meta.inferenceReason}`;
3067
- }
3068
- if (meta.truncated) {
3069
- note += ` Field list trimmed to ${meta.usedFields} of ${meta.totalFields} available fields.`;
3070
- }
3071
- return note;
3072
- }
3073
-
3074
- function normalizeLayoutMode(value: any): 'variable' | 'text' {
3075
- const normalized = normalizeOptionalString(value).toLowerCase();
3076
- return normalized === 'text' ? 'text' : 'variable';
3077
- }
3078
-
3079
- function normalizeFilterCondition(value: any): string {
3080
- const allowed = new Set(['eq', 'ne', 'lt', 'lte', 'gt', 'gte', 'bw', 'nnull', 'null', 'regex']);
3081
- const normalized = normalizeOptionalString(value).toLowerCase();
3082
- return allowed.has(normalized) ? normalized : '';
3083
- }
3084
-
3085
- function normalizeSortOrder(value: any): string {
3086
- const normalized = normalizeOptionalString(value).toLowerCase();
3087
- return normalized === 'dsc' || normalized === 'desc' ? 'dsc' : 'asc';
3088
- }
3089
-
3090
- function normalizeTotalType(value: any): string {
3091
- const normalized = normalizeOptionalString(value).toLowerCase();
3092
- if (normalized === 'avg' || normalized === 'sum') {
3093
- return normalized;
3094
- }
3095
- return '';
3096
- }
3097
-
3098
- function normalizeDateInterval(value: any): string {
3099
- const normalized = normalizeOptionalString(value);
3100
- if (!normalized) {
3101
- return '';
3102
- }
3103
- const map: Record<string, string> = {
3104
- seconds: 'Seconds',
3105
- minutes: 'Minutes',
3106
- hours: 'Hours',
3107
- daily: 'Daily',
3108
- weekly: 'Weekly',
3109
- monthly: 'Monthly',
3110
- quarterly: 'Quarterly',
3111
- yearly: 'Yearly'
3112
- };
3113
- const key = normalized.toLowerCase();
3114
- return map[key] || '';
3115
- }
3116
-
3117
- function normalizeFilterValue(value: any, fieldType?: string): any {
3118
- if (fieldType === 'Number') {
3119
- const num = Number(value);
3120
- return Number.isFinite(num) ? num : value;
3121
- }
3122
- if (fieldType === 'Boolean') {
3123
- if (value === true || value === false) {
3124
- return value;
3125
- }
3126
- if (typeof value === 'string') {
3127
- return value.toLowerCase() === 'true';
3128
- }
3129
- return value;
3130
- }
3131
- if (fieldType === 'Date') {
3132
- if (value instanceof Date) {
3133
- return value;
3134
- }
3135
- if (typeof value === 'string') {
3136
- const parsed = new Date(value);
3137
- return isNaN(parsed.getTime()) ? value : parsed.toISOString();
3138
- }
3139
- }
3140
- return value;
3141
- }
3142
-
3143
- function normalizeBooleanValue(value: any): boolean | undefined {
3144
- if (value === true || value === false) {
3145
- return value;
3146
- }
3147
- if (typeof value === 'string') {
3148
- if (value.toLowerCase() === 'true') {
3149
- return true;
3150
- }
3151
- if (value.toLowerCase() === 'false') {
3152
- return false;
3153
- }
3154
- }
3155
- return undefined;
3156
- }
3157
-
3158
- function resolveCollectionName(value: any, ctx: ReturnType<typeof buildSanitizerContext>): string {
3159
- const key = normalizeKey(value);
3160
- if (!key) {
3161
- return '';
3162
- }
3163
- const found = ctx.collectionIndex.get(key);
3164
- return found ? found.collection : '';
3165
- }
3166
-
3167
- function resolveFieldPath(value: any, ctx: ReturnType<typeof buildSanitizerContext>): string {
3168
- const pathKey = normalizePathKey(value);
3169
- if (pathKey && ctx.fieldIndex.has(pathKey)) {
3170
- return ctx.fieldIndex.get(pathKey)?.field_path || '';
3171
- }
3172
- const nameKey = normalizeKey(value);
3173
- if (nameKey && ctx.fieldNameIndex.has(nameKey)) {
3174
- return ctx.fieldNameIndex.get(nameKey) || '';
3175
- }
3176
- return '';
3177
- }
3178
-
3179
- function sanitizeAlias(value: any): string {
3180
- const alias = normalizeOptionalString(value);
3181
- if (!alias) {
3182
- return '';
3183
- }
3184
- return alias.replace(/[^\w\- ]/g, '').trim().slice(0, 40);
3185
- }
3186
-
3187
- function normalizeKey(value: any): string {
3188
- return normalizeOptionalString(value).toLowerCase().replace(/[^a-z0-9]+/g, '');
3189
- }
3190
-
3191
- function normalizePathKey(value: any): string {
3192
- return normalizeOptionalString(value).toLowerCase();
3193
- }
3194
-
3195
- function resolveArrayRoot(path: string): string {
3196
- const raw = normalizeOptionalString(path);
3197
- if (!raw) {
3198
- return '';
3199
- }
3200
- const idx = raw.indexOf('.$');
3201
- if (idx === -1) {
3202
- return '';
3203
- }
3204
- return raw.slice(0, idx);
3205
- }
3206
-
3207
- function isLinkCandidateFieldPath(path: string): boolean {
3208
- const normalized = normalizeOptionalString(path).toLowerCase();
3209
- if (!normalized) {
3210
- return false;
3211
- }
3212
- const last = (normalized.split('.').pop() || '').replace(/\$/g, '');
3213
- if (!last) {
3214
- return false;
3215
- }
3216
- if (last === 'id' || last === '_id') {
3217
- return true;
3218
- }
3219
- if (last.endsWith('_id') || last.endsWith('_uuid') || last.endsWith('_guid')) {
3220
- return true;
3221
- }
3222
- if (last.startsWith('id_')) {
3223
- return true;
3224
- }
3225
- return false;
3226
- }
3227
-
3228
- function safeJsonParse(value: string): any {
3229
- if (!value) {
3230
- return null;
3231
- }
3232
- try {
3233
- return JSON.parse(value);
3234
- }
3235
- catch {
3236
- const match = value.match(/\{[\s\S]*\}/);
3237
- if (!match) {
3238
- return null;
3239
- }
3240
- try {
3241
- return JSON.parse(match[0]);
3242
- }
3243
- catch {
3244
- return null;
3245
- }
3246
- }
3247
- }
3248
-
3249
- function evaluateReportBuilderGuardrails(message: string): GuardrailResult | null {
3250
- const normalized = String(message || '').toLowerCase();
3251
- const patterns: Array<{ pattern: RegExp; reason: string }> = [
3252
- { pattern: /\b(source\s*code|full\s*code|entire\s*code|repo\s*dump|repository|git\s*clone)\b/i, reason: 'Code access is restricted.' },
3253
- { pattern: /\b(credentials?|passwords?|secrets?|tokens?)\b/i, reason: 'Credentials and secrets are restricted.' },
3254
- { pattern: /\b(delete|drop|truncate)\s+(database|db|schema|table)\b/i, reason: 'Database operations are restricted.' },
3255
- { pattern: /\b(shell|terminal|ssh|sudo|rm\s+-rf|chmod|chown)\b/i, reason: 'Server operations are restricted.' },
3256
- { pattern: /\b(exploit|bypass|malware|phishing|ransomware|ddos|sql\s*injection)\b/i, reason: 'Security abuse is restricted.' },
3257
- { pattern: /\b(credit\s*card|social\s*security|ssn|bank\s*account)\b/i, reason: 'Sensitive personal data is restricted.' }
3258
- ];
3259
- for (const entry of patterns) {
3260
- if (entry.pattern.test(normalized)) {
3261
- return {
3262
- blocked: true,
3263
- reason: entry.reason,
3264
- response: 'I can’t help with that request because it could access restricted systems or data. Please describe the report you need (fields, filters, timeframe) and I can configure it safely.'
3265
- };
3266
- }
3267
- }
3268
- return null;
3269
- }
3270
-
3271
- async function resolveIsSuperAdmin(id_user?: string): Promise<boolean> {
3272
- if (!id_user) {
3273
- return false;
3274
- }
3275
- try {
3276
- const user = await Users.findById(id_user);
3277
- return !!user?.roles?.super_admin;
3278
- }
3279
- catch {
3280
- return false;
3281
- }
3282
- }
3283
-
3284
- async function resolveClientId(idClientInput?: string, idUser?: string): Promise<string> {
3285
- const normalized = normalizeOptionalString(idClientInput);
3286
- if (normalized) {
3287
- return normalized;
3288
- }
3289
- if (!idUser) {
3290
- return '';
3291
- }
3292
- try {
3293
- const user = await Users.findById(idUser);
3294
- const fromOther = normalizeOptionalString(user?.other?.id_client || user?.other?.idClient);
3295
- return fromOther;
3296
- }
3297
- catch {
3298
- return '';
3299
- }
3300
- }