@talkpilot/core-db 1.1.14 → 1.1.16

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 (291) hide show
  1. package/.cursor/rules/development.mdc +65 -65
  2. package/DEVELOPMENT.md +141 -98
  3. package/README.md +297 -160
  4. package/README_OLD.md +160 -0
  5. package/dist/connection.d.ts.map +1 -1
  6. package/dist/connection.js +10 -0
  7. package/dist/connection.js.map +1 -1
  8. package/dist/index.d.ts +4 -0
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +8 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/municipal/index.d.ts +1 -0
  13. package/dist/municipal/index.d.ts.map +1 -1
  14. package/dist/municipal/index.js +1 -0
  15. package/dist/municipal/index.js.map +1 -1
  16. package/dist/municipal/muniIssues/index.d.ts +7 -0
  17. package/dist/municipal/muniIssues/index.d.ts.map +1 -0
  18. package/dist/municipal/muniIssues/index.js +29 -0
  19. package/dist/municipal/muniIssues/index.js.map +1 -0
  20. package/dist/municipal/muniIssues/muniIssues.constants.d.ts +14 -0
  21. package/dist/municipal/muniIssues/muniIssues.constants.d.ts.map +1 -0
  22. package/dist/municipal/muniIssues/muniIssues.constants.js +17 -0
  23. package/dist/municipal/muniIssues/muniIssues.constants.js.map +1 -0
  24. package/dist/municipal/muniIssues/muniIssues.getters.d.ts +7 -0
  25. package/dist/municipal/muniIssues/muniIssues.getters.d.ts.map +1 -0
  26. package/dist/municipal/muniIssues/muniIssues.getters.js +24 -0
  27. package/dist/municipal/muniIssues/muniIssues.getters.js.map +1 -0
  28. package/dist/municipal/muniIssues/muniIssues.schema.d.ts +4 -0
  29. package/dist/municipal/muniIssues/muniIssues.schema.d.ts.map +1 -0
  30. package/dist/municipal/muniIssues/muniIssues.schema.js +63 -0
  31. package/dist/municipal/muniIssues/muniIssues.schema.js.map +1 -0
  32. package/dist/municipal/muniIssues/muniIssues.setters.d.ts +4 -0
  33. package/dist/municipal/muniIssues/muniIssues.setters.d.ts.map +1 -0
  34. package/dist/municipal/muniIssues/muniIssues.setters.js +22 -0
  35. package/dist/municipal/muniIssues/muniIssues.setters.js.map +1 -0
  36. package/dist/municipal/muniIssues/muniIssues.setup.d.ts +3 -0
  37. package/dist/municipal/muniIssues/muniIssues.setup.d.ts.map +1 -0
  38. package/dist/municipal/muniIssues/muniIssues.setup.js +61 -0
  39. package/dist/municipal/muniIssues/muniIssues.setup.js.map +1 -0
  40. package/dist/municipal/muniIssues/muniIssues.types.d.ts +30 -0
  41. package/dist/municipal/muniIssues/muniIssues.types.d.ts.map +1 -0
  42. package/dist/municipal/muniIssues/muniIssues.types.js +3 -0
  43. package/dist/municipal/muniIssues/muniIssues.types.js.map +1 -0
  44. package/dist/municipal/tickets/index.d.ts +2 -1
  45. package/dist/municipal/tickets/index.d.ts.map +1 -1
  46. package/dist/municipal/tickets/index.js +1 -0
  47. package/dist/municipal/tickets/index.js.map +1 -1
  48. package/dist/municipal/tickets/tickets.constants.d.ts +7 -0
  49. package/dist/municipal/tickets/tickets.constants.d.ts.map +1 -0
  50. package/dist/municipal/tickets/tickets.constants.js +10 -0
  51. package/dist/municipal/tickets/tickets.constants.js.map +1 -0
  52. package/dist/municipal/tickets/tickets.getters.d.ts +0 -11
  53. package/dist/municipal/tickets/tickets.getters.d.ts.map +1 -1
  54. package/dist/municipal/tickets/tickets.getters.js +0 -128
  55. package/dist/municipal/tickets/tickets.getters.js.map +1 -1
  56. package/dist/municipal/tickets/tickets.statistics.aggregation.d.ts +69 -0
  57. package/dist/municipal/tickets/tickets.statistics.aggregation.d.ts.map +1 -0
  58. package/dist/municipal/tickets/tickets.statistics.aggregation.js +99 -0
  59. package/dist/municipal/tickets/tickets.statistics.aggregation.js.map +1 -0
  60. package/dist/municipal/tickets/tickets.statistics.getters.d.ts +12 -0
  61. package/dist/municipal/tickets/tickets.statistics.getters.d.ts.map +1 -0
  62. package/dist/municipal/tickets/tickets.statistics.getters.js +80 -0
  63. package/dist/municipal/tickets/tickets.statistics.getters.js.map +1 -0
  64. package/dist/municipal/tickets/tickets.types.d.ts +15 -5
  65. package/dist/municipal/tickets/tickets.types.d.ts.map +1 -1
  66. package/dist/talkpilot/calls/calls.constants.d.ts +17 -0
  67. package/dist/talkpilot/calls/calls.constants.d.ts.map +1 -0
  68. package/dist/talkpilot/calls/calls.constants.js +20 -0
  69. package/dist/talkpilot/calls/calls.constants.js.map +1 -0
  70. package/dist/talkpilot/calls/calls.dashboard.d.ts +3 -0
  71. package/dist/talkpilot/calls/calls.dashboard.d.ts.map +1 -0
  72. package/dist/talkpilot/calls/calls.dashboard.js +191 -0
  73. package/dist/talkpilot/calls/calls.dashboard.js.map +1 -0
  74. package/dist/talkpilot/calls/calls.getters.d.ts +3 -2
  75. package/dist/talkpilot/calls/calls.getters.d.ts.map +1 -1
  76. package/dist/talkpilot/calls/calls.getters.js +1 -2
  77. package/dist/talkpilot/calls/calls.getters.js.map +1 -1
  78. package/dist/talkpilot/calls/calls.statistics.getters.d.ts +28 -0
  79. package/dist/talkpilot/calls/calls.statistics.getters.d.ts.map +1 -0
  80. package/dist/talkpilot/calls/calls.statistics.getters.js +453 -0
  81. package/dist/talkpilot/calls/calls.statistics.getters.js.map +1 -0
  82. package/dist/talkpilot/calls/calls.statistics.types.d.ts +43 -0
  83. package/dist/talkpilot/calls/calls.statistics.types.d.ts.map +1 -0
  84. package/dist/talkpilot/calls/calls.statistics.types.js +3 -0
  85. package/dist/talkpilot/calls/calls.statistics.types.js.map +1 -0
  86. package/dist/talkpilot/calls/calls.types.d.ts +8 -7
  87. package/dist/talkpilot/calls/calls.types.d.ts.map +1 -1
  88. package/dist/talkpilot/calls/calls.types.js +0 -3
  89. package/dist/talkpilot/calls/calls.types.js.map +1 -1
  90. package/dist/talkpilot/calls/dashboard/calls.dashboard.d.ts +4 -0
  91. package/dist/talkpilot/calls/dashboard/calls.dashboard.d.ts.map +1 -0
  92. package/dist/talkpilot/calls/dashboard/calls.dashboard.js +223 -0
  93. package/dist/talkpilot/calls/dashboard/calls.dashboard.js.map +1 -0
  94. package/dist/talkpilot/calls/dashboard/calls.dashboard.types.d.ts +45 -0
  95. package/dist/talkpilot/calls/dashboard/calls.dashboard.types.d.ts.map +1 -0
  96. package/dist/talkpilot/calls/dashboard/calls.dashboard.types.js +3 -0
  97. package/dist/talkpilot/calls/dashboard/calls.dashboard.types.js.map +1 -0
  98. package/dist/talkpilot/calls/index.d.ts +4 -0
  99. package/dist/talkpilot/calls/index.d.ts.map +1 -1
  100. package/dist/talkpilot/calls/index.js +4 -0
  101. package/dist/talkpilot/calls/index.js.map +1 -1
  102. package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts +2 -1
  103. package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts.map +1 -1
  104. package/dist/talkpilot/clientsConfig/clientsConfig.getters.js +14 -0
  105. package/dist/talkpilot/clientsConfig/clientsConfig.getters.js.map +1 -1
  106. package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts +20 -7
  107. package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts.map +1 -1
  108. package/dist/talkpilot/clientsConfig/clientsConfig.types.js +6 -0
  109. package/dist/talkpilot/clientsConfig/clientsConfig.types.js.map +1 -1
  110. package/dist/talkpilot/contextNotes/contextNotes.getters.d.ts +19 -0
  111. package/dist/talkpilot/contextNotes/contextNotes.getters.d.ts.map +1 -0
  112. package/dist/talkpilot/contextNotes/contextNotes.getters.js +55 -0
  113. package/dist/talkpilot/contextNotes/contextNotes.getters.js.map +1 -0
  114. package/dist/talkpilot/contextNotes/contextNotes.types.d.ts +19 -0
  115. package/dist/talkpilot/contextNotes/contextNotes.types.d.ts.map +1 -0
  116. package/dist/talkpilot/contextNotes/contextNotes.types.js +3 -0
  117. package/dist/talkpilot/contextNotes/contextNotes.types.js.map +1 -0
  118. package/dist/talkpilot/contextNotes/index.d.ts +3 -0
  119. package/dist/talkpilot/contextNotes/index.d.ts.map +1 -0
  120. package/dist/talkpilot/contextNotes/index.js +19 -0
  121. package/dist/talkpilot/contextNotes/index.js.map +1 -0
  122. package/dist/talkpilot/flows/flows.schema.d.ts +3 -0
  123. package/dist/talkpilot/flows/flows.schema.d.ts.map +1 -1
  124. package/dist/talkpilot/flows/flows.schema.js +2 -1
  125. package/dist/talkpilot/flows/flows.schema.js.map +1 -1
  126. package/dist/talkpilot/flows/flows.types.d.ts +2 -0
  127. package/dist/talkpilot/flows/flows.types.d.ts.map +1 -1
  128. package/dist/talkpilot/index.d.ts +3 -0
  129. package/dist/talkpilot/index.d.ts.map +1 -1
  130. package/dist/talkpilot/index.js +3 -0
  131. package/dist/talkpilot/index.js.map +1 -1
  132. package/dist/talkpilot/phone_numbers/index.d.ts +2 -2
  133. package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts +1 -1
  134. package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts.map +1 -1
  135. package/dist/talkpilot/phone_numbers/phone_numbers.getter.js +5 -3
  136. package/dist/talkpilot/phone_numbers/phone_numbers.getter.js.map +1 -1
  137. package/dist/talkpilot/phone_numbers/phone_numbers.schema.js +12 -12
  138. package/dist/talkpilot/phone_numbers/phone_numbers.types.d.ts +4 -4
  139. package/dist/talkpilot/products/index.d.ts +3 -0
  140. package/dist/talkpilot/products/index.d.ts.map +1 -0
  141. package/dist/talkpilot/products/index.js +19 -0
  142. package/dist/talkpilot/products/index.js.map +1 -0
  143. package/dist/talkpilot/products/products.getters.d.ts +6 -0
  144. package/dist/talkpilot/products/products.getters.d.ts.map +1 -0
  145. package/dist/talkpilot/products/products.getters.js +14 -0
  146. package/dist/talkpilot/products/products.getters.js.map +1 -0
  147. package/dist/talkpilot/products/products.types.d.ts +9 -0
  148. package/dist/talkpilot/products/products.types.d.ts.map +1 -0
  149. package/dist/talkpilot/products/products.types.js +3 -0
  150. package/dist/talkpilot/products/products.types.js.map +1 -0
  151. package/dist/talkpilot/results/index.d.ts +1 -1
  152. package/dist/talkpilot/results/index.d.ts.map +1 -1
  153. package/dist/talkpilot/results/index.js +3 -1
  154. package/dist/talkpilot/results/index.js.map +1 -1
  155. package/dist/talkpilot/results/results.getter.d.ts +3 -1
  156. package/dist/talkpilot/results/results.getter.d.ts.map +1 -1
  157. package/dist/talkpilot/results/results.getter.js +11 -2
  158. package/dist/talkpilot/results/results.getter.js.map +1 -1
  159. package/dist/talkpilot/retry_analyze/index.d.ts +3 -0
  160. package/dist/talkpilot/retry_analyze/index.d.ts.map +1 -0
  161. package/dist/talkpilot/retry_analyze/index.js +19 -0
  162. package/dist/talkpilot/retry_analyze/index.js.map +1 -0
  163. package/dist/talkpilot/retry_analyze/retryAnalyze.getters.d.ts +10 -0
  164. package/dist/talkpilot/retry_analyze/retryAnalyze.getters.d.ts.map +1 -0
  165. package/dist/talkpilot/retry_analyze/retryAnalyze.getters.js +60 -0
  166. package/dist/talkpilot/retry_analyze/retryAnalyze.getters.js.map +1 -0
  167. package/dist/talkpilot/retry_analyze/retryAnalyze.types.d.ts +12 -0
  168. package/dist/talkpilot/retry_analyze/retryAnalyze.types.d.ts.map +1 -0
  169. package/dist/talkpilot/retry_analyze/retryAnalyze.types.js +3 -0
  170. package/dist/talkpilot/retry_analyze/retryAnalyze.types.js.map +1 -0
  171. package/dist/test-utils/db-utils.d.ts.map +1 -1
  172. package/dist/test-utils/db-utils.js +2 -0
  173. package/dist/test-utils/db-utils.js.map +1 -1
  174. package/dist/test-utils/factories/index.d.ts +3 -0
  175. package/dist/test-utils/factories/index.d.ts.map +1 -1
  176. package/dist/test-utils/factories/index.js +3 -0
  177. package/dist/test-utils/factories/index.js.map +1 -1
  178. package/dist/test-utils/factories/municipal/muniIssues.d.ts +10 -0
  179. package/dist/test-utils/factories/municipal/muniIssues.d.ts.map +1 -0
  180. package/dist/test-utils/factories/municipal/muniIssues.js +30 -0
  181. package/dist/test-utils/factories/municipal/muniIssues.js.map +1 -0
  182. package/dist/test-utils/factories/talkpilot/contextNotes.d.ts +16 -0
  183. package/dist/test-utils/factories/talkpilot/contextNotes.d.ts.map +1 -0
  184. package/dist/test-utils/factories/talkpilot/contextNotes.js +33 -0
  185. package/dist/test-utils/factories/talkpilot/contextNotes.js.map +1 -0
  186. package/dist/test-utils/factories/websitalk/scans.d.ts +5 -0
  187. package/dist/test-utils/factories/websitalk/scans.d.ts.map +1 -0
  188. package/dist/test-utils/factories/websitalk/scans.js +25 -0
  189. package/dist/test-utils/factories/websitalk/scans.js.map +1 -0
  190. package/dist/utils/date.utils.d.ts +49 -0
  191. package/dist/utils/date.utils.d.ts.map +1 -0
  192. package/dist/utils/date.utils.js +108 -0
  193. package/dist/utils/date.utils.js.map +1 -0
  194. package/dist/utils/shared.types.d.ts +5 -0
  195. package/dist/utils/shared.types.d.ts.map +1 -0
  196. package/dist/utils/shared.types.js +3 -0
  197. package/dist/utils/shared.types.js.map +1 -0
  198. package/dist/websitalk/index.d.ts +7 -0
  199. package/dist/websitalk/index.d.ts.map +1 -0
  200. package/dist/websitalk/index.js +34 -0
  201. package/dist/websitalk/index.js.map +1 -0
  202. package/dist/websitalk/mongodb-client.d.ts +13 -0
  203. package/dist/websitalk/mongodb-client.d.ts.map +1 -0
  204. package/dist/websitalk/mongodb-client.js +56 -0
  205. package/dist/websitalk/mongodb-client.js.map +1 -0
  206. package/dist/websitalk/scans/index.d.ts +3 -0
  207. package/dist/websitalk/scans/index.d.ts.map +1 -0
  208. package/dist/websitalk/scans/index.js +19 -0
  209. package/dist/websitalk/scans/index.js.map +1 -0
  210. package/dist/websitalk/scans/scans.getters.d.ts +12 -0
  211. package/dist/websitalk/scans/scans.getters.d.ts.map +1 -0
  212. package/dist/websitalk/scans/scans.getters.js +74 -0
  213. package/dist/websitalk/scans/scans.getters.js.map +1 -0
  214. package/dist/websitalk/scans/scans.types.d.ts +45 -0
  215. package/dist/websitalk/scans/scans.types.d.ts.map +1 -0
  216. package/dist/websitalk/scans/scans.types.js +3 -0
  217. package/dist/websitalk/scans/scans.types.js.map +1 -0
  218. package/jest.config.js +20 -19
  219. package/package.json +46 -45
  220. package/src/connection.ts +12 -0
  221. package/src/index.ts +10 -0
  222. package/src/municipal/index.ts +1 -0
  223. package/src/municipal/muniIssues/__tests__/muniIssues.setters.spec.ts +94 -0
  224. package/src/municipal/muniIssues/__tests__/muniIssues.setup.spec.ts +92 -0
  225. package/src/municipal/muniIssues/index.ts +17 -0
  226. package/src/municipal/muniIssues/muniIssues.constants.ts +18 -0
  227. package/src/municipal/muniIssues/muniIssues.getters.ts +24 -0
  228. package/src/municipal/muniIssues/muniIssues.schema.ts +66 -0
  229. package/src/municipal/muniIssues/muniIssues.setters.ts +24 -0
  230. package/src/municipal/muniIssues/muniIssues.setup.ts +65 -0
  231. package/src/municipal/muniIssues/muniIssues.types.ts +37 -0
  232. package/src/municipal/tickets/__tests__/tickets.getters.spec.ts +1 -37
  233. package/src/municipal/tickets/__tests__/tickets.statistics.spec.ts +99 -0
  234. package/src/municipal/tickets/index.ts +9 -2
  235. package/src/municipal/tickets/tickets.constants.ts +8 -0
  236. package/src/municipal/tickets/tickets.getters.ts +0 -140
  237. package/src/municipal/tickets/tickets.statistics.aggregation.ts +110 -0
  238. package/src/municipal/tickets/tickets.statistics.getters.ts +132 -0
  239. package/src/municipal/tickets/tickets.types.ts +54 -43
  240. package/src/talkpilot/calls/__tests__/calls.dashboard.spec.ts +149 -0
  241. package/src/talkpilot/calls/__tests__/calls.spec.ts +48 -30
  242. package/src/talkpilot/calls/__tests__/calls.statistics.spec.ts +481 -0
  243. package/src/talkpilot/calls/calls.constants.ts +20 -0
  244. package/src/talkpilot/calls/calls.getters.ts +6 -6
  245. package/src/talkpilot/calls/calls.statistics.getters.ts +668 -0
  246. package/src/talkpilot/calls/calls.statistics.types.ts +48 -0
  247. package/src/talkpilot/calls/calls.types.ts +18 -14
  248. package/src/talkpilot/calls/dashboard/calls.dashboard.ts +292 -0
  249. package/src/talkpilot/calls/dashboard/calls.dashboard.types.ts +57 -0
  250. package/src/talkpilot/calls/index.ts +4 -0
  251. package/src/talkpilot/clientsConfig/__tests__/clientsConfig.getters.spec.ts +53 -0
  252. package/src/talkpilot/clientsConfig/__tests__/clientsConfig.spec.ts +91 -7
  253. package/src/talkpilot/clientsConfig/clientsConfig.getters.ts +34 -1
  254. package/src/talkpilot/clientsConfig/clientsConfig.types.ts +127 -92
  255. package/src/talkpilot/contextNotes/__tests__/contextNotes.getters.spec.ts +217 -0
  256. package/src/talkpilot/contextNotes/contextNotes.getters.ts +85 -0
  257. package/src/talkpilot/contextNotes/contextNotes.types.ts +20 -0
  258. package/src/talkpilot/contextNotes/index.ts +2 -0
  259. package/src/talkpilot/flows/__tests__/flows.schema.spec.ts +31 -2
  260. package/src/talkpilot/flows/flows.schema.ts +2 -1
  261. package/src/talkpilot/flows/flows.types.ts +3 -0
  262. package/src/talkpilot/index.ts +3 -0
  263. package/src/talkpilot/phone_numbers/__tests__/phone_numbers.spec.ts +40 -35
  264. package/src/talkpilot/phone_numbers/index.ts +2 -2
  265. package/src/talkpilot/phone_numbers/phone_numbers.getter.ts +10 -6
  266. package/src/talkpilot/phone_numbers/phone_numbers.schema.ts +12 -12
  267. package/src/talkpilot/phone_numbers/phone_numbers.types.ts +4 -4
  268. package/src/talkpilot/products/__tests__/products.getters.spec.ts +44 -0
  269. package/src/talkpilot/products/index.ts +2 -0
  270. package/src/talkpilot/products/products.getters.ts +12 -0
  271. package/src/talkpilot/products/products.types.ts +9 -0
  272. package/src/talkpilot/results/index.ts +2 -0
  273. package/src/talkpilot/results/results.getter.ts +14 -2
  274. package/src/talkpilot/retry_analyze/__tests__/retryAnalyze.getters.spec.ts +156 -0
  275. package/src/talkpilot/retry_analyze/index.ts +2 -0
  276. package/src/talkpilot/retry_analyze/retryAnalyze.getters.ts +84 -0
  277. package/src/talkpilot/retry_analyze/retryAnalyze.types.ts +13 -0
  278. package/src/test-utils/db-utils.ts +3 -1
  279. package/src/test-utils/factories/index.ts +3 -0
  280. package/src/test-utils/factories/municipal/muniIssues.ts +32 -0
  281. package/src/test-utils/factories/talkpilot/contextNotes.ts +40 -0
  282. package/src/test-utils/factories/websitalk/scans.ts +23 -0
  283. package/src/utils/date.utils.ts +126 -0
  284. package/src/utils/shared.types.ts +4 -0
  285. package/src/websitalk/index.ts +15 -0
  286. package/src/websitalk/mongodb-client.ts +61 -0
  287. package/src/websitalk/scans/__tests__/scans.spec.ts +218 -0
  288. package/src/websitalk/scans/index.ts +2 -0
  289. package/src/websitalk/scans/scans.getters.ts +113 -0
  290. package/src/websitalk/scans/scans.types.ts +53 -0
  291. package/tsconfig.json +23 -23
@@ -1,8 +1,11 @@
1
1
  import { ObjectId, Sort, WithId } from "mongodb";
2
2
  import { TranscriptionSegment } from "../results";
3
+ import { LeadProperty } from "../leads";
3
4
 
4
- export const CONFERENCE_ROLE_CUSTOMER = "customer" as const;
5
- export const CONFERENCE_ROLE_SUPERVISOR = "supervisor" as const;
5
+ import {
6
+ CONFERENCE_ROLE_CUSTOMER,
7
+ CONFERENCE_ROLE_SUPERVISOR,
8
+ } from "./calls.constants";
6
9
 
7
10
  export type ConferenceRole =
8
11
  | typeof CONFERENCE_ROLE_CUSTOMER
@@ -23,7 +26,7 @@ export type Call = {
23
26
  callLength: number;
24
27
  transcription?: TranscriptionSegment[];
25
28
  status?: string;
26
- leads?: Record<string, string>[];
29
+ leads?: LeadProperty[];
27
30
  summary?: string;
28
31
  recordingUrl?: string;
29
32
  env: string;
@@ -47,21 +50,21 @@ export type ToolExecution = {
47
50
  durationMs: number;
48
51
  args: Record<string, unknown> | { _redacted: true };
49
52
  meta: ToolExecutionMeta;
50
- status: 'success' | 'error';
53
+ status: "success" | "error";
51
54
  httpStatus?: number;
52
55
  response?: Record<string, unknown>;
53
56
  };
54
57
 
55
58
  export type ToolExecutionMeta =
56
- | {
57
- kind: 'http';
58
- url: string;
59
- method: string;
60
- flowToolId?: string;
61
- runInBackground?: boolean;
62
- sensitive?: boolean;
63
- }
64
- | { kind: 'internal' };
59
+ | {
60
+ kind: "http";
61
+ url: string;
62
+ method: string;
63
+ flowToolId?: string;
64
+ runInBackground?: boolean;
65
+ sensitive?: boolean;
66
+ }
67
+ | { kind: "internal" };
65
68
 
66
69
  export type CallQueryOptions = {
67
70
  sort?: Sort;
@@ -102,7 +105,8 @@ export type CallUpdateParams = Partial<Omit<Call, ImmutableCallFields>>;
102
105
 
103
106
  export type CallDoc = WithId<Call>;
104
107
 
105
- export type CallsByHour = { hour: string; calls: number };
108
+ /** @deprecated use DashboardHeatmapMetric instead */
109
+ export type CallsByHour = { hour: number; calls: number };
106
110
 
107
111
  export type CountOpts = {
108
112
  isOutgoingCall?: boolean;
@@ -0,0 +1,292 @@
1
+ import dayjs from "dayjs";
2
+ import { getCallsCollection } from "../calls.getters";
3
+ import { getClientConfig } from "../../clientsConfig";
4
+ import utc from "dayjs/plugin/utc";
5
+ import timezonePlugin from "dayjs/plugin/timezone";
6
+ import isoWeek from "dayjs/plugin/isoWeek";
7
+ import quarterOfYear from "dayjs/plugin/quarterOfYear";
8
+ import {
9
+ DashboardAggregationResult,
10
+ DashboardDailyTrendMetric,
11
+ DashboardReportQuery,
12
+ DashboardReportResponse,
13
+ DashboardSummaryMetrics,
14
+ DashboardVolumeGranularity,
15
+ } from "./calls.dashboard.types";
16
+
17
+ export type {
18
+ DashboardVolumeGranularity,
19
+ DashboardReportQuery,
20
+ DashboardReportResponse,
21
+ } from "./calls.dashboard.types";
22
+
23
+ const DEFAULT_KPI_DATA = {
24
+ completedCount: 0,
25
+ busyCount: 0,
26
+ answeredDuration: 0,
27
+ };
28
+
29
+ dayjs.extend(utc);
30
+ dayjs.extend(timezonePlugin);
31
+ dayjs.extend(isoWeek);
32
+ dayjs.extend(quarterOfYear);
33
+
34
+ const BUCKET_UNIT: Record<DashboardVolumeGranularity, string> = {
35
+ hour: "hour",
36
+ day: "day",
37
+ week: "isoWeek",
38
+ month: "month",
39
+ quarter: "quarter",
40
+ year: "year",
41
+ };
42
+
43
+ function formatBucketKey(
44
+ bucketStart: dayjs.Dayjs,
45
+ granularity: DashboardVolumeGranularity,
46
+ ): string {
47
+ switch (granularity) {
48
+ case "hour":
49
+ return bucketStart.format("YYYY-MM-DDTHH:00");
50
+ case "day":
51
+ case "week":
52
+ return bucketStart.format("YYYY-MM-DD");
53
+ case "month":
54
+ return bucketStart.format("YYYY-MM");
55
+ case "quarter":
56
+ return `${bucketStart.format("YYYY")}-Q${bucketStart.quarter()}`;
57
+ case "year":
58
+ return bucketStart.format("YYYY");
59
+ }
60
+ }
61
+
62
+ function generateBucketKeys(
63
+ startDateObj: Date,
64
+ endDateObj: Date,
65
+ granularity: DashboardVolumeGranularity,
66
+ timezone: string,
67
+ ): string[] {
68
+ const unitName = BUCKET_UNIT[granularity];
69
+ const startOfUnit = unitName as dayjs.OpUnitType;
70
+ const stepUnit = (
71
+ unitName === "isoWeek" ? "week" : unitName
72
+ ) as dayjs.ManipulateType;
73
+ const end = dayjs(endDateObj).tz(timezone);
74
+ let cursor = dayjs(startDateObj).tz(timezone).startOf(startOfUnit);
75
+
76
+ const keys: string[] = [];
77
+ while (cursor.isBefore(end) || cursor.isSame(end, startOfUnit)) {
78
+ keys.push(formatBucketKey(cursor, granularity));
79
+ cursor = cursor.add(1, stepUnit);
80
+ }
81
+ return keys;
82
+ }
83
+
84
+ function fillVolumeGaps(
85
+ bucketKeys: string[],
86
+ volumeDataRaw: { date: string; completed: number }[],
87
+ ): DashboardDailyTrendMetric[] {
88
+ const completedByKey = new Map(
89
+ volumeDataRaw.map((d) => [d.date, d.completed]),
90
+ );
91
+ return bucketKeys.map((date) => ({
92
+ date,
93
+ completed: completedByKey.get(date) ?? 0,
94
+ }));
95
+ }
96
+
97
+ function resolveVolumeGranularity(
98
+ startDateObj: Date,
99
+ endDateObj: Date,
100
+ ): DashboardVolumeGranularity {
101
+ const spanDays = dayjs(endDateObj).diff(dayjs(startDateObj), "day");
102
+
103
+ if (spanDays <= 2) return "hour";
104
+ if (spanDays <= 60) return "day";
105
+ if (spanDays <= 365) return "week";
106
+ if (spanDays <= 730) return "month";
107
+ if (spanDays <= 1460) return "quarter";
108
+ return "year";
109
+ }
110
+
111
+ const VOLUME_DATE_FORMATS: Partial<Record<DashboardVolumeGranularity, string>> =
112
+ {
113
+ hour: "%Y-%m-%dT%H:00",
114
+ day: "%Y-%m-%d",
115
+ month: "%Y-%m",
116
+ year: "%Y",
117
+ };
118
+
119
+ function buildVolumeBucketIdExpression(
120
+ timezone: string,
121
+ granularity: DashboardVolumeGranularity,
122
+ ) {
123
+ if (granularity === "week") {
124
+ return {
125
+ $dateToString: {
126
+ format: "%Y-%m-%d",
127
+ date: {
128
+ $dateTrunc: {
129
+ date: "$createdAt",
130
+ unit: "week",
131
+ timezone,
132
+ startOfWeek: "monday",
133
+ },
134
+ },
135
+ timezone,
136
+ },
137
+ };
138
+ }
139
+
140
+ if (granularity === "quarter") {
141
+ return {
142
+ $concat: [
143
+ { $toString: { $year: { date: "$createdAt", timezone } } },
144
+ "-Q",
145
+ {
146
+ $toString: {
147
+ $add: [
148
+ {
149
+ $trunc: {
150
+ $divide: [
151
+ {
152
+ $subtract: [
153
+ { $month: { date: "$createdAt", timezone } },
154
+ 1,
155
+ ],
156
+ },
157
+ 3,
158
+ ],
159
+ },
160
+ },
161
+ 1,
162
+ ],
163
+ },
164
+ },
165
+ ],
166
+ };
167
+ }
168
+
169
+ return {
170
+ $dateToString: {
171
+ format: VOLUME_DATE_FORMATS[granularity],
172
+ date: "$createdAt",
173
+ timezone,
174
+ },
175
+ };
176
+ }
177
+
178
+ function buildKpisPipeline() {
179
+ return [
180
+ {
181
+ $group: {
182
+ _id: null,
183
+ completedCount: {
184
+ $sum: { $cond: [{ $eq: ["$status", "completed"] }, 1, 0] },
185
+ },
186
+ busyCount: {
187
+ $sum: { $cond: [{ $eq: ["$status", "busy"] }, 1, 0] },
188
+ },
189
+ answeredDuration: {
190
+ $sum: {
191
+ $cond: [
192
+ { $in: ["$status", ["completed", "busy"]] },
193
+ "$callLength",
194
+ 0,
195
+ ],
196
+ },
197
+ },
198
+ },
199
+ },
200
+ ];
201
+ }
202
+
203
+ function buildVolumeDataPipeline(
204
+ timezone: string,
205
+ granularity: DashboardVolumeGranularity,
206
+ ) {
207
+ return [
208
+ {
209
+ $group: {
210
+ _id: buildVolumeBucketIdExpression(timezone, granularity),
211
+ completed: {
212
+ $sum: { $cond: [{ $eq: ["$status", "completed"] }, 1, 0] },
213
+ },
214
+ },
215
+ },
216
+ { $sort: { _id: 1 } },
217
+ ];
218
+ }
219
+
220
+ export async function getDashboardStats(
221
+ params: DashboardReportQuery,
222
+ ): Promise<DashboardReportResponse> {
223
+ const {
224
+ clientId,
225
+ startDate,
226
+ endDate,
227
+ granularity: requestedGranularity,
228
+ } = params;
229
+ const clientConfig = await getClientConfig(clientId);
230
+ const timezone = clientConfig?.timezone ?? "UTC";
231
+
232
+ const startDateObj = dayjs(startDate).tz(timezone).startOf("day").toDate();
233
+ const endDateObj = dayjs(endDate).tz(timezone).endOf("day").toDate();
234
+
235
+ const granularity =
236
+ requestedGranularity ?? resolveVolumeGranularity(startDateObj, endDateObj);
237
+
238
+ const pipeline = [
239
+ {
240
+ $match: {
241
+ clientId,
242
+ createdAt: { $gte: startDateObj, $lte: endDateObj },
243
+ },
244
+ },
245
+ {
246
+ $facet: {
247
+ kpis: buildKpisPipeline(),
248
+ volumeData: buildVolumeDataPipeline(timezone, granularity),
249
+ },
250
+ },
251
+ ];
252
+
253
+ const callsCollection = getCallsCollection();
254
+ const [aggregatedResult] = await callsCollection
255
+ .aggregate<DashboardAggregationResult>(pipeline)
256
+ .toArray();
257
+
258
+ const kpiData = aggregatedResult?.kpis?.[0] ?? DEFAULT_KPI_DATA;
259
+ const volumeDataRaw = aggregatedResult?.volumeData ?? [];
260
+
261
+ const answeredCount = kpiData.completedCount + kpiData.busyCount;
262
+ const answeredDuration = kpiData.answeredDuration ?? 0;
263
+
264
+ const kpis: DashboardSummaryMetrics = {
265
+ totalCalls: answeredCount,
266
+ avgDurationSeconds:
267
+ answeredCount > 0 ? Math.round(answeredDuration / answeredCount) : 0,
268
+ timeSavedMinutes: Math.round(answeredDuration / 60),
269
+ completedCount: kpiData.completedCount,
270
+ busyCount: kpiData.busyCount,
271
+ };
272
+
273
+ const bucketKeys = generateBucketKeys(
274
+ startDateObj,
275
+ endDateObj,
276
+ granularity,
277
+ timezone,
278
+ );
279
+ const volumeData: DashboardDailyTrendMetric[] = fillVolumeGaps(
280
+ bucketKeys,
281
+ volumeDataRaw.map((d) => ({ date: d._id, completed: d.completed })),
282
+ );
283
+
284
+ const response: DashboardReportResponse = {
285
+ kpis,
286
+ charts: {
287
+ volumeData,
288
+ volumeGranularity: granularity,
289
+ },
290
+ };
291
+ return response;
292
+ }
@@ -0,0 +1,57 @@
1
+ export type DashboardHeatmapMetric = {
2
+ hour: number;
3
+ calls: number;
4
+ };
5
+
6
+ export type DashboardDailyTrendMetric = {
7
+ date: string;
8
+ completed: number;
9
+ };
10
+
11
+ export type DashboardVolumeGranularity =
12
+ | "hour"
13
+ | "day"
14
+ | "week"
15
+ | "month"
16
+ | "quarter"
17
+ | "year";
18
+
19
+ export type DashboardSummaryMetrics = {
20
+ totalCalls: number;
21
+ avgDurationSeconds: number;
22
+ timeSavedMinutes: number;
23
+ completedCount: number;
24
+ busyCount: number;
25
+ };
26
+
27
+ export type DashboardVisualData = {
28
+ volumeData: DashboardDailyTrendMetric[];
29
+ volumeGranularity: DashboardVolumeGranularity;
30
+ };
31
+
32
+ export type DashboardReportQuery = {
33
+ clientId: string;
34
+ startDate: string;
35
+ endDate: string;
36
+ granularity?: DashboardVolumeGranularity;
37
+ };
38
+
39
+ export type DashboardReportResponse = {
40
+ kpis: DashboardSummaryMetrics;
41
+ charts: DashboardVisualData;
42
+ };
43
+ type RawKpiResult = {
44
+ completedCount: number;
45
+ busyCount: number;
46
+ answeredDuration: number | null;
47
+ };
48
+
49
+ export type RawVolumeAggregationResult = {
50
+ _id: string;
51
+ completed: number;
52
+ };
53
+
54
+ export interface DashboardAggregationResult {
55
+ kpis: RawKpiResult[];
56
+ volumeData: RawVolumeAggregationResult[];
57
+ }
@@ -1,2 +1,6 @@
1
+ export * from "./calls.constants";
1
2
  export * from "./calls.types";
2
3
  export * from "./calls.getters";
4
+ export * from "./calls.statistics.types";
5
+ export * from "./calls.statistics.getters";
6
+ export * from "./dashboard/calls.dashboard";
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
2
+ import {
3
+ getClientsConfigCollection,
4
+ updateProductConfig,
5
+ } from "../clientsConfig.getters";
6
+
7
+ describe("updateProductConfig", () => {
8
+ beforeEach(async () => {
9
+ await getClientsConfigCollection().deleteMany({});
10
+ });
11
+
12
+ afterEach(async () => {
13
+ await getClientsConfigCollection().deleteMany({});
14
+ });
15
+
16
+ it("should update partial fields using correct dot notation", async () => {
17
+ const clientId = "client-123";
18
+ const collection = getClientsConfigCollection();
19
+
20
+ await collection.insertOne({
21
+ clientId,
22
+ products: {
23
+ websiteTalk: {
24
+ language: "English",
25
+ companyName: "OldCorp",
26
+ defaultBaseUrl: "https://old-url.com",
27
+ logoUrl: "",
28
+ timezone: "UTC",
29
+ },
30
+ },
31
+ } as any);
32
+
33
+ const updates = { language: "Hebrew", companyName: "TestCorp" };
34
+ await updateProductConfig(clientId, "websiteTalk", updates);
35
+
36
+ const updatedDoc = await collection.findOne({ clientId });
37
+
38
+ expect(updatedDoc).toBeDefined();
39
+ expect(updatedDoc?.products.websiteTalk?.language).toBe("Hebrew");
40
+ expect(updatedDoc?.products.websiteTalk?.companyName).toBe("TestCorp");
41
+ expect(updatedDoc?.products.websiteTalk?.defaultBaseUrl).toBe(
42
+ "https://old-url.com",
43
+ );
44
+ });
45
+
46
+ it("should throw an error if no client config is found", async () => {
47
+ await expect(
48
+ updateProductConfig("invalid-client", "websiteTalk", {
49
+ language: "English",
50
+ }),
51
+ ).rejects.toThrow("No client config found for clientId: invalid-client");
52
+ });
53
+ });
@@ -36,10 +36,6 @@ describe("db.clientsConfig", () => {
36
36
  timezone: "Asia/Jerusalem",
37
37
  language: "he",
38
38
  defaultBaseUrl: "https://acme.example",
39
- scheduler: {
40
- type: "cron",
41
- schedule: "0 9 * * 1-5"
42
- },
43
39
  },
44
40
  },
45
41
  });
@@ -57,9 +53,6 @@ describe("db.clientsConfig", () => {
57
53
  timezone: "Asia/Jerusalem",
58
54
  language: "he",
59
55
  defaultBaseUrl: "https://acme.example",
60
- scheduler: {
61
- type: "cron",
62
- },
63
56
  },
64
57
  },
65
58
  });
@@ -70,6 +63,97 @@ describe("db.clientsConfig", () => {
70
63
  expect(result).toBeNull();
71
64
  });
72
65
 
66
+ it("given moked_106 with both toolsPrompts when saved and retrieved then both prompts are persisted", async () => {
67
+ // Given
68
+ const clientId = "tools-prompts-both";
69
+ const clientConfig = createClientConfig({
70
+ clientId,
71
+ products: {
72
+ municipal: {
73
+ cityName: "ashdod",
74
+ timezone: "Asia/Jerusalem",
75
+ moked_106: {
76
+ toolsPrompts: {
77
+ findSubjectsSystemPrompt: "Custom subjects prompt",
78
+ findStreetsSystemPrompt: "Custom streets prompt",
79
+ },
80
+ },
81
+ },
82
+ },
83
+ });
84
+
85
+ // When
86
+ await createClientConfigDoc(clientConfig);
87
+ const result = await getClientConfig(clientId);
88
+
89
+ // Then
90
+ expect(result?.products.municipal?.moked_106?.toolsPrompts).toMatchObject(
91
+ {
92
+ findSubjectsSystemPrompt: "Custom subjects prompt",
93
+ findStreetsSystemPrompt: "Custom streets prompt",
94
+ },
95
+ );
96
+ });
97
+
98
+ it("given moked_106 with findSubjectsSystemPrompt only when saved and retrieved then findStreetsSystemPrompt is undefined", async () => {
99
+ // Given
100
+ const clientId = "tools-prompts-subjects-only";
101
+ const clientConfig = createClientConfig({
102
+ clientId,
103
+ products: {
104
+ municipal: {
105
+ cityName: "ashdod",
106
+ timezone: "Asia/Jerusalem",
107
+ moked_106: {
108
+ toolsPrompts: {
109
+ findSubjectsSystemPrompt: "Subjects only prompt",
110
+ },
111
+ },
112
+ },
113
+ },
114
+ });
115
+
116
+ // When
117
+ await createClientConfigDoc(clientConfig);
118
+ const result = await getClientConfig(clientId);
119
+
120
+ // Then
121
+ expect(
122
+ result?.products.municipal?.moked_106?.toolsPrompts
123
+ ?.findSubjectsSystemPrompt,
124
+ ).toBe("Subjects only prompt");
125
+ expect(
126
+ result?.products.municipal?.moked_106?.toolsPrompts
127
+ ?.findStreetsSystemPrompt,
128
+ ).toBeUndefined();
129
+ });
130
+
131
+ it("given moked_106 without toolsPrompts when saved and retrieved then toolsPrompts is undefined", async () => {
132
+ // Given
133
+ const clientId = "tools-prompts-absent";
134
+ const clientConfig = createClientConfig({
135
+ clientId,
136
+ products: {
137
+ municipal: {
138
+ cityName: "ashdod",
139
+ timezone: "Asia/Jerusalem",
140
+ moked_106: {
141
+ avoidSaveTickets: true,
142
+ },
143
+ },
144
+ },
145
+ });
146
+
147
+ // When
148
+ await createClientConfigDoc(clientConfig);
149
+ const result = await getClientConfig(clientId);
150
+
151
+ // Then
152
+ expect(
153
+ result?.products.municipal?.moked_106?.toolsPrompts,
154
+ ).toBeUndefined();
155
+ });
156
+
73
157
  it("should support communications config", async () => {
74
158
  const clientId = "communications-test";
75
159
  const clientConfig = createClientConfig({
@@ -1,4 +1,11 @@
1
- import { ClientConfigDoc, getDb, findClientByPhoneNumber } from "../index";
1
+ import {
2
+ ClientConfigDoc,
3
+ getDb,
4
+ findClientByPhoneNumber,
5
+ Products,
6
+ KNOWN_PRODUCT_KEYS,
7
+ KnownProductKey,
8
+ } from "../index";
2
9
  import { Collection } from "mongodb";
3
10
 
4
11
  export const getClientsConfigCollection = (): Collection<ClientConfigDoc> => {
@@ -20,3 +27,29 @@ export const createClientConfigDoc = async (
20
27
  ): Promise<void> => {
21
28
  await getClientsConfigCollection().insertOne(clientConfig);
22
29
  };
30
+
31
+ export async function updateProductConfig<K extends KnownProductKey>(
32
+ clientId: string,
33
+ productKey: K,
34
+ updates: Partial<Products[K]>,
35
+ ): Promise<void> {
36
+ if (!KNOWN_PRODUCT_KEYS.includes(productKey as KnownProductKey)) {
37
+ throw new Error(
38
+ `Invalid product key: "${productKey}". Must be one of: ${KNOWN_PRODUCT_KEYS.join(", ")}`,
39
+ );
40
+ }
41
+
42
+ const setOperations: Record<string, unknown> = {};
43
+ for (const [key, value] of Object.entries(updates)) {
44
+ setOperations[`products.${productKey}.${key}`] = value;
45
+ }
46
+
47
+ const result = await getClientsConfigCollection().updateOne(
48
+ { clientId },
49
+ { $set: setOperations },
50
+ );
51
+
52
+ if (result.matchedCount === 0) {
53
+ throw new Error(`No client config found for clientId: ${clientId}`);
54
+ }
55
+ }