@talkpilot/core-db 1.3.45 → 1.3.47

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 (215) hide show
  1. package/.cursor/rules/development.mdc +65 -65
  2. package/AGENTS.md +49 -0
  3. package/DEVELOPMENT.md +141 -141
  4. package/README.md +747 -747
  5. package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts +1 -0
  6. package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts.map +1 -1
  7. package/jest.config.js +20 -20
  8. package/package.json +46 -46
  9. package/src/__tests__/setup.ts +20 -20
  10. package/src/bulkWrite/__tests__/bulkWrite.spec.ts +65 -65
  11. package/src/bulkWrite/bulkWrite.ops.ts +45 -45
  12. package/src/bulkWrite/bulkWrite.types.ts +4 -4
  13. package/src/bulkWrite/index.ts +2 -2
  14. package/src/config.ts +9 -9
  15. package/src/configuration/index.ts +17 -17
  16. package/src/configuration/internalModels/index.ts +3 -3
  17. package/src/configuration/internalModels/internalModels.constants.ts +1 -1
  18. package/src/configuration/internalModels/internalModels.getters.ts +33 -33
  19. package/src/configuration/internalModels/internalModels.types.ts +12 -12
  20. package/src/configuration/models/index.ts +25 -25
  21. package/src/configuration/models/models.constants.ts +13 -13
  22. package/src/configuration/models/models.getters.ts +53 -53
  23. package/src/configuration/models/models.types.ts +79 -79
  24. package/src/configuration/mongodb-client.ts +62 -62
  25. package/src/configuration/prompts/index.ts +2 -2
  26. package/src/configuration/prompts/prompts.getters.ts +49 -49
  27. package/src/configuration/prompts/prompts.types.ts +11 -11
  28. package/src/connection.ts +81 -81
  29. package/src/index.ts +46 -46
  30. package/src/municipal/__tests__/validation.spec.ts +62 -62
  31. package/src/municipal/cities/cities.getters.ts +50 -50
  32. package/src/municipal/cities/cities.types.ts +11 -11
  33. package/src/municipal/cities/index.ts +2 -2
  34. package/src/municipal/departmentsSubjects/__tests__/departmentsSubjects.spec.ts +272 -272
  35. package/src/municipal/departmentsSubjects/departmentsSubjects.getters.ts +317 -317
  36. package/src/municipal/departmentsSubjects/departmentsSubjects.types.ts +73 -73
  37. package/src/municipal/departmentsSubjects/index.ts +9 -9
  38. package/src/municipal/index.ts +22 -22
  39. package/src/municipal/mongodb-client.ts +61 -61
  40. package/src/municipal/muniIssues/__tests__/muniIssues.setters.spec.ts +94 -94
  41. package/src/municipal/muniIssues/__tests__/muniIssues.setup.spec.ts +92 -92
  42. package/src/municipal/muniIssues/index.ts +17 -17
  43. package/src/municipal/muniIssues/muniIssues.constants.ts +18 -18
  44. package/src/municipal/muniIssues/muniIssues.getters.ts +24 -24
  45. package/src/municipal/muniIssues/muniIssues.schema.ts +66 -66
  46. package/src/municipal/muniIssues/muniIssues.setters.ts +24 -24
  47. package/src/municipal/muniIssues/muniIssues.setup.ts +65 -65
  48. package/src/municipal/muniIssues/muniIssues.types.ts +37 -37
  49. package/src/municipal/streets/__tests__/streets.spec.ts +253 -253
  50. package/src/municipal/streets/index.ts +2 -2
  51. package/src/municipal/streets/streets.getters.ts +140 -140
  52. package/src/municipal/streets/streets.types.ts +19 -19
  53. package/src/municipal/systemInstructions/__tests__/getters.spec.ts +113 -113
  54. package/src/municipal/systemInstructions/__tests__/setters.spec.ts +274 -274
  55. package/src/municipal/systemInstructions/index.ts +7 -7
  56. package/src/municipal/systemInstructions/instructions.getters.ts +57 -57
  57. package/src/municipal/systemInstructions/instructions.setters.ts +119 -119
  58. package/src/municipal/systemInstructions/instructions.types.ts +30 -30
  59. package/src/municipal/tickets/__tests__/tickets.getters.spec.ts +30 -30
  60. package/src/municipal/tickets/__tests__/tickets.statistics.spec.ts +99 -99
  61. package/src/municipal/tickets/index.ts +9 -9
  62. package/src/municipal/tickets/tickets.constants.ts +8 -8
  63. package/src/municipal/tickets/tickets.getters.ts +121 -121
  64. package/src/municipal/tickets/tickets.statistics.aggregation.ts +110 -110
  65. package/src/municipal/tickets/tickets.statistics.getters.ts +145 -145
  66. package/src/municipal/tickets/tickets.types.ts +54 -54
  67. package/src/municipal/utils/types.ts +11 -11
  68. package/src/talkpilot/__tests__/db.spec.ts +38 -38
  69. package/src/talkpilot/__tests__/mongodb-client.spec.ts +18 -18
  70. package/src/talkpilot/__tests__/validation.spec.ts +68 -68
  71. package/src/talkpilot/agents/__tests__/agents.getters.spec.ts +29 -29
  72. package/src/talkpilot/agents/agents.getters.ts +34 -34
  73. package/src/talkpilot/agents/agents.types.ts +14 -14
  74. package/src/talkpilot/agents/index.ts +2 -2
  75. package/src/talkpilot/backgroundToolResults/__tests__/backgroundToolResults.getters.spec.ts +147 -147
  76. package/src/talkpilot/backgroundToolResults/backgroundToolResults.getters.ts +65 -65
  77. package/src/talkpilot/backgroundToolResults/backgroundToolResults.types.ts +23 -23
  78. package/src/talkpilot/backgroundToolResults/index.ts +2 -2
  79. package/src/talkpilot/calls/__tests__/callStats.utils.spec.ts +128 -128
  80. package/src/talkpilot/calls/__tests__/calls.dashboard.spec.ts +229 -229
  81. package/src/talkpilot/calls/__tests__/calls.spec.ts +471 -471
  82. package/src/talkpilot/calls/__tests__/calls.statistics.spec.ts +483 -483
  83. package/src/talkpilot/calls/calls.constants.ts +49 -49
  84. package/src/talkpilot/calls/calls.getters.ts +277 -277
  85. package/src/talkpilot/calls/calls.statistics.getters.ts +668 -668
  86. package/src/talkpilot/calls/calls.statistics.types.ts +48 -48
  87. package/src/talkpilot/calls/calls.types.ts +134 -134
  88. package/src/talkpilot/calls/dashboard/calls.dashboard.ts +295 -295
  89. package/src/talkpilot/calls/dashboard/calls.dashboard.types.ts +57 -57
  90. package/src/talkpilot/calls/index.ts +6 -6
  91. package/src/talkpilot/clientAudioBuffers/__tests__/clientAudioBuffer.getters.spec.ts +160 -160
  92. package/src/talkpilot/clientAudioBuffers/clientAudioBuffer.getters.ts +117 -117
  93. package/src/talkpilot/clientAudioBuffers/clientsAudioBuffers.types.ts +25 -25
  94. package/src/talkpilot/clientAudioBuffers/index.ts +2 -2
  95. package/src/talkpilot/clients/__tests__/clients.quota.spec.ts +123 -123
  96. package/src/talkpilot/clients/clients.getters.ts +16 -16
  97. package/src/talkpilot/clients/clients.quota.getters.ts +119 -119
  98. package/src/talkpilot/clients/clients.quota.types.ts +27 -27
  99. package/src/talkpilot/clients/clients.types.ts +18 -18
  100. package/src/talkpilot/clients/index.ts +4 -4
  101. package/src/talkpilot/clientsConfig/__tests__/clientsConfig.getters.spec.ts +423 -423
  102. package/src/talkpilot/clientsConfig/__tests__/clientsConfig.spec.ts +212 -212
  103. package/src/talkpilot/clientsConfig/clientsConfig.constants.ts +2 -2
  104. package/src/talkpilot/clientsConfig/clientsConfig.getters.ts +247 -247
  105. package/src/talkpilot/clientsConfig/clientsConfig.types.ts +136 -135
  106. package/src/talkpilot/clientsConfig/index.ts +3 -3
  107. package/src/talkpilot/contextNotes/__tests__/contextNotes.getters.spec.ts +217 -217
  108. package/src/talkpilot/contextNotes/contextNotes.getters.ts +85 -85
  109. package/src/talkpilot/contextNotes/contextNotes.types.ts +20 -20
  110. package/src/talkpilot/contextNotes/index.ts +2 -2
  111. package/src/talkpilot/flows/__tests__/flows.schema.spec.ts +99 -99
  112. package/src/talkpilot/flows/flows.getter.ts +14 -14
  113. package/src/talkpilot/flows/flows.schema.ts +155 -155
  114. package/src/talkpilot/flows/flows.types.ts +189 -189
  115. package/src/talkpilot/flows/index.ts +2 -2
  116. package/src/talkpilot/groups/__tests__/groups.spec.ts +90 -90
  117. package/src/talkpilot/groups/__tests__/phone.utils.spec.ts +32 -32
  118. package/src/talkpilot/groups/groups.getters.ts +30 -30
  119. package/src/talkpilot/groups/groups.types.ts +29 -29
  120. package/src/talkpilot/groups/index.ts +3 -3
  121. package/src/talkpilot/groups/phone.utils.ts +46 -46
  122. package/src/talkpilot/index.ts +31 -31
  123. package/src/talkpilot/leads/index.ts +2 -2
  124. package/src/talkpilot/leads/leads.getter.ts +6 -6
  125. package/src/talkpilot/leads/leads.schema.ts +33 -33
  126. package/src/talkpilot/leads/leads.types.ts +20 -20
  127. package/src/talkpilot/mongodb-client.ts +78 -78
  128. package/src/talkpilot/phone_numbers/__tests__/phone_numbers.spec.ts +282 -282
  129. package/src/talkpilot/phone_numbers/index.ts +2 -2
  130. package/src/talkpilot/phone_numbers/phone_numbers.getter.ts +176 -176
  131. package/src/talkpilot/phone_numbers/phone_numbers.schema.ts +17 -17
  132. package/src/talkpilot/phone_numbers/phone_numbers.types.ts +30 -30
  133. package/src/talkpilot/plans/__tests__/plans.spec.ts +70 -70
  134. package/src/talkpilot/plans/index.ts +2 -2
  135. package/src/talkpilot/plans/plans.getters.ts +132 -132
  136. package/src/talkpilot/plans/plans.types.ts +89 -89
  137. package/src/talkpilot/products/__tests__/products.getters.spec.ts +44 -44
  138. package/src/talkpilot/products/index.ts +2 -2
  139. package/src/talkpilot/products/products.getters.ts +12 -12
  140. package/src/talkpilot/products/products.types.ts +9 -9
  141. package/src/talkpilot/results/index.ts +7 -7
  142. package/src/talkpilot/results/results.getter.ts +39 -39
  143. package/src/talkpilot/results/results.schema.ts +25 -25
  144. package/src/talkpilot/results/results.types.ts +34 -34
  145. package/src/talkpilot/retry_analyze/__tests__/retryAnalyze.getters.spec.ts +156 -156
  146. package/src/talkpilot/retry_analyze/index.ts +2 -2
  147. package/src/talkpilot/retry_analyze/retryAnalyze.getters.ts +84 -84
  148. package/src/talkpilot/retry_analyze/retryAnalyze.types.ts +13 -13
  149. package/src/talkpilot/sessions/__tests__/sessions.spec.ts +147 -147
  150. package/src/talkpilot/sessions/index.ts +2 -2
  151. package/src/talkpilot/sessions/sessions.getter.ts +92 -92
  152. package/src/talkpilot/sessions/sessions.schema.ts +34 -34
  153. package/src/talkpilot/sessions/sessions.types.ts +30 -30
  154. package/src/talkpilot/subscriptions/__tests__/subscriptions.getters.utils.spec.ts +45 -45
  155. package/src/talkpilot/subscriptions/index.ts +3 -3
  156. package/src/talkpilot/subscriptions/subscriptions.getters.ts +146 -146
  157. package/src/talkpilot/subscriptions/subscriptions.getters.utils.ts +33 -33
  158. package/src/talkpilot/subscriptions/subscriptions.types.ts +66 -66
  159. package/src/talkpilot/utils/__tests__/query.utils.spec.ts +49 -49
  160. package/src/talkpilot/utils/query.utils.ts +21 -21
  161. package/src/test-utils/db-utils.ts +33 -33
  162. package/src/test-utils/factories/index.ts +18 -18
  163. package/src/test-utils/factories/municipal/cities.ts +16 -16
  164. package/src/test-utils/factories/municipal/departmentsSubjects.ts +37 -37
  165. package/src/test-utils/factories/municipal/muniIssues.ts +32 -32
  166. package/src/test-utils/factories/municipal/streets.ts +22 -22
  167. package/src/test-utils/factories/municipal/tickets.ts +39 -39
  168. package/src/test-utils/factories/talkpilot/agents.ts +19 -19
  169. package/src/test-utils/factories/talkpilot/calls.ts +37 -37
  170. package/src/test-utils/factories/talkpilot/clientAudioBuffers.ts +20 -20
  171. package/src/test-utils/factories/talkpilot/clientsConfig.ts +18 -18
  172. package/src/test-utils/factories/talkpilot/contextNotes.ts +40 -40
  173. package/src/test-utils/factories/talkpilot/flows.ts +33 -33
  174. package/src/test-utils/factories/talkpilot/groups.ts +33 -33
  175. package/src/test-utils/factories/talkpilot/phone_numbers.ts +22 -22
  176. package/src/test-utils/factories/talkpilot/sessions.ts +35 -35
  177. package/src/test-utils/factories/websitalk/knowledgeDocuments.ts +29 -29
  178. package/src/test-utils/factories/websitalk/scanResources.ts +26 -26
  179. package/src/test-utils/factories/websitalk/scans.ts +29 -29
  180. package/src/test-utils/factories/websitalk/websiteUrls.ts +17 -17
  181. package/src/utils/date.utils.ts +126 -126
  182. package/src/utils/shared.types.ts +4 -4
  183. package/src/utils/validation.ts +23 -23
  184. package/src/websitalk/index.ts +18 -18
  185. package/src/websitalk/knowledgeDocuments/__tests__/knowledgeDocuments.spec.ts +186 -186
  186. package/src/websitalk/knowledgeDocuments/index.ts +3 -3
  187. package/src/websitalk/knowledgeDocuments/knowledgeDocuments.constants.ts +17 -17
  188. package/src/websitalk/knowledgeDocuments/knowledgeDocuments.getters.ts +78 -78
  189. package/src/websitalk/knowledgeDocuments/knowledgeDocuments.types.ts +62 -62
  190. package/src/websitalk/mongodb-client.ts +61 -61
  191. package/src/websitalk/scanResources/__tests__/scanResources.spec.ts +136 -136
  192. package/src/websitalk/scanResources/index.ts +4 -4
  193. package/src/websitalk/scanResources/scanResources.constants.ts +47 -47
  194. package/src/websitalk/scanResources/scanResources.getters.ts +94 -94
  195. package/src/websitalk/scanResources/scanResources.types.ts +90 -90
  196. package/src/websitalk/scanResources/scanResources.utils.ts +28 -28
  197. package/src/websitalk/scans/__tests__/scans.spec.ts +313 -313
  198. package/src/websitalk/scans/index.ts +4 -4
  199. package/src/websitalk/scans/scans.constants.ts +15 -15
  200. package/src/websitalk/scans/scans.getters.ts +166 -166
  201. package/src/websitalk/scans/scans.types.ts +54 -54
  202. package/src/websitalk/scans/scans.utils.ts +5 -5
  203. package/src/websitalk/websiteUrls/__tests__/websiteUrls.spec.ts +77 -77
  204. package/src/websitalk/websiteUrls/index.ts +2 -2
  205. package/src/websitalk/websiteUrls/websiteUrls.getters.ts +65 -65
  206. package/src/websitalk/websiteUrls/websiteUrls.types.ts +13 -13
  207. package/tsconfig.json +23 -23
  208. package/dist/talkpilot/clients/clients.quota.constants.d.ts +0 -2
  209. package/dist/talkpilot/clients/clients.quota.constants.d.ts.map +0 -1
  210. package/dist/talkpilot/clients/clients.quota.constants.js +0 -5
  211. package/dist/talkpilot/clients/clients.quota.constants.js.map +0 -1
  212. package/dist/talkpilot/clients/clients.quota.utils.d.ts +0 -8
  213. package/dist/talkpilot/clients/clients.quota.utils.d.ts.map +0 -1
  214. package/dist/talkpilot/clients/clients.quota.utils.js +0 -45
  215. package/dist/talkpilot/clients/clients.quota.utils.js.map +0 -1
@@ -1,229 +1,229 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
2
- import { getDashboardStats } from "../dashboard/calls.dashboard";
3
- import { getCallsCollection } from "../calls.getters";
4
- import { getClientsConfigCollection } from "../../clientsConfig/clientsConfig.getters";
5
-
6
- describe("getDashboardStats", () => {
7
- beforeEach(async () => {
8
- await getCallsCollection().deleteMany({});
9
- await getClientsConfigCollection().deleteMany({});
10
- });
11
-
12
- afterEach(async () => {
13
- await getCallsCollection().deleteMany({});
14
- await getClientsConfigCollection().deleteMany({});
15
- });
16
-
17
- it("should return correct empty dashboard stats when no calls exist", async () => {
18
- const clientId = "client-dash-123";
19
-
20
- await getClientsConfigCollection().insertOne({
21
- clientId,
22
- timezone: "UTC",
23
- products: {},
24
- } as any);
25
-
26
- const params = {
27
- clientId,
28
- startDate: "2026-05-01T00:00:00.000Z",
29
- endDate: "2026-05-31T23:59:59.999Z",
30
- };
31
-
32
- const result = await getDashboardStats(params);
33
-
34
- expect(result).toBeDefined();
35
- expect(result.kpis.totalCalls).toBe(0);
36
- expect(result.kpis.completedCount).toBe(0);
37
- expect(result.kpis.avgDurationSeconds).toBe(0);
38
- expect(result.kpis.inCallCount).toBe(0);
39
- expect(result.kpis.timeSavedMinutes).toBe(0);
40
- // ~30 day range -> daily buckets, gap-filled with zeros across the whole range
41
- expect(result.charts.volumeGranularity).toBe("day");
42
- expect(result.charts.volumeData.length).toBeGreaterThan(0);
43
- expect(result.charts.volumeData.every((d) => d.completed === 0)).toBe(true);
44
- expect(result.charts.volumeData[0].date).toBe("2026-05-01");
45
- expect(
46
- result.charts.volumeData[result.charts.volumeData.length - 1].date,
47
- ).toBe("2026-05-31");
48
- });
49
-
50
- it("should pick a bucket size that keeps the volume chart readable across range lengths", async () => {
51
- const clientId = "client-dash-456";
52
-
53
- await getClientsConfigCollection().insertOne({
54
- clientId,
55
- timezone: "UTC",
56
- products: {},
57
- } as any);
58
-
59
- const oneDay = await getDashboardStats({
60
- clientId,
61
- startDate: "2026-05-01T00:00:00.000Z",
62
- endDate: "2026-05-01T23:59:59.999Z",
63
- });
64
- expect(oneDay.charts.volumeGranularity).toBe("hour");
65
-
66
- const threeWeeks = await getDashboardStats({
67
- clientId,
68
- startDate: "2026-05-01T00:00:00.000Z",
69
- endDate: "2026-05-21T23:59:59.999Z",
70
- });
71
- expect(threeWeeks.charts.volumeGranularity).toBe("day");
72
-
73
- const fourMonths = await getDashboardStats({
74
- clientId,
75
- startDate: "2026-01-01T00:00:00.000Z",
76
- endDate: "2026-04-30T23:59:59.999Z",
77
- });
78
- expect(fourMonths.charts.volumeGranularity).toBe("week");
79
-
80
- const eighteenMonths = await getDashboardStats({
81
- clientId,
82
- startDate: "2025-01-01T00:00:00.000Z",
83
- endDate: "2026-06-30T23:59:59.999Z",
84
- });
85
- expect(eighteenMonths.charts.volumeGranularity).toBe("month");
86
-
87
- const threeYears = await getDashboardStats({
88
- clientId,
89
- startDate: "2023-01-01T00:00:00.000Z",
90
- endDate: "2026-01-01T23:59:59.999Z",
91
- });
92
- expect(threeYears.charts.volumeGranularity).toBe("quarter");
93
-
94
- const fiveYears = await getDashboardStats({
95
- clientId,
96
- startDate: "2021-01-01T00:00:00.000Z",
97
- endDate: "2026-01-01T23:59:59.999Z",
98
- });
99
- expect(fiveYears.charts.volumeGranularity).toBe("year");
100
- });
101
-
102
- it("should fill gaps with zero-count buckets so the chart has no missing points", async () => {
103
- const clientId = "client-dash-789";
104
-
105
- await getClientsConfigCollection().insertOne({
106
- clientId,
107
- timezone: "UTC",
108
- products: {},
109
- } as any);
110
-
111
- // Sparse data: only two of the day's hours have calls.
112
- await getCallsCollection().insertMany([
113
- {
114
- clientId,
115
- status: "completed",
116
- callLength: 60,
117
- createdAt: new Date("2026-05-01T09:15:00Z"),
118
- },
119
- {
120
- clientId,
121
- status: "completed",
122
- callLength: 90,
123
- createdAt: new Date("2026-05-01T18:45:00Z"),
124
- },
125
- ] as any);
126
-
127
- const result = await getDashboardStats({
128
- clientId,
129
- startDate: "2026-05-01T00:00:00.000Z",
130
- endDate: "2026-05-01T23:59:59.999Z",
131
- });
132
-
133
- expect(result.charts.volumeGranularity).toBe("hour");
134
- // A full day of hourly buckets, with no gaps, sorted chronologically.
135
- expect(result.charts.volumeData).toHaveLength(24);
136
- expect(result.charts.volumeData[0].date).toBe("2026-05-01T00:00");
137
- expect(result.charts.volumeData[23].date).toBe("2026-05-01T23:00");
138
-
139
- const populated = result.charts.volumeData.filter((d) => d.completed > 0);
140
- expect(populated).toHaveLength(2);
141
- expect(populated.map((d) => d.date)).toEqual([
142
- "2026-05-01T09:00",
143
- "2026-05-01T18:00",
144
- ]);
145
-
146
- const empty = result.charts.volumeData.filter((d) => d.completed === 0);
147
- expect(empty).toHaveLength(22);
148
- });
149
-
150
- it("should count in-progress and answered as inCallCount, not completed", async () => {
151
- const clientId = "client-dash-in-call";
152
-
153
- await getClientsConfigCollection().insertOne({
154
- clientId,
155
- timezone: "UTC",
156
- products: {},
157
- } as any);
158
-
159
- await getCallsCollection().insertMany([
160
- {
161
- clientId,
162
- status: "in-progress",
163
- callLength: 30,
164
- createdAt: new Date("2026-05-10T10:00:00Z"),
165
- },
166
- {
167
- clientId,
168
- status: "answered",
169
- callLength: 45,
170
- createdAt: new Date("2026-05-10T11:00:00Z"),
171
- },
172
- {
173
- clientId,
174
- status: "completed",
175
- callLength: 120,
176
- createdAt: new Date("2026-05-10T12:00:00Z"),
177
- },
178
- ] as any);
179
-
180
- const result = await getDashboardStats({
181
- clientId,
182
- startDate: "2026-05-01T00:00:00.000Z",
183
- endDate: "2026-05-31T23:59:59.999Z",
184
- });
185
-
186
- expect(result.kpis.inCallCount).toBe(2);
187
- expect(result.kpis.completedCount).toBe(1);
188
- expect(result.kpis.totalCalls).toBe(3);
189
- expect(result.kpis.avgDurationSeconds).toBe(120);
190
- expect(result.kpis.timeSavedMinutes).toBe(2);
191
- });
192
-
193
- it("should not count physical busy status as inCall or answered duration", async () => {
194
- const clientId = "client-dash-busy";
195
-
196
- await getClientsConfigCollection().insertOne({
197
- clientId,
198
- timezone: "UTC",
199
- products: {},
200
- } as any);
201
-
202
- await getCallsCollection().insertMany([
203
- {
204
- clientId,
205
- status: "busy",
206
- callLength: 60,
207
- createdAt: new Date("2026-05-10T10:00:00Z"),
208
- },
209
- {
210
- clientId,
211
- status: "completed",
212
- callLength: 90,
213
- createdAt: new Date("2026-05-10T11:00:00Z"),
214
- },
215
- ] as any);
216
-
217
- const result = await getDashboardStats({
218
- clientId,
219
- startDate: "2026-05-01T00:00:00.000Z",
220
- endDate: "2026-05-31T23:59:59.999Z",
221
- });
222
-
223
- expect(result.kpis.inCallCount).toBe(0);
224
- expect(result.kpis.completedCount).toBe(1);
225
- expect(result.kpis.totalCalls).toBe(1);
226
- expect(result.kpis.avgDurationSeconds).toBe(90);
227
- expect(result.kpis.timeSavedMinutes).toBe(2);
228
- });
229
- });
1
+ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
2
+ import { getDashboardStats } from "../dashboard/calls.dashboard";
3
+ import { getCallsCollection } from "../calls.getters";
4
+ import { getClientsConfigCollection } from "../../clientsConfig/clientsConfig.getters";
5
+
6
+ describe("getDashboardStats", () => {
7
+ beforeEach(async () => {
8
+ await getCallsCollection().deleteMany({});
9
+ await getClientsConfigCollection().deleteMany({});
10
+ });
11
+
12
+ afterEach(async () => {
13
+ await getCallsCollection().deleteMany({});
14
+ await getClientsConfigCollection().deleteMany({});
15
+ });
16
+
17
+ it("should return correct empty dashboard stats when no calls exist", async () => {
18
+ const clientId = "client-dash-123";
19
+
20
+ await getClientsConfigCollection().insertOne({
21
+ clientId,
22
+ timezone: "UTC",
23
+ products: {},
24
+ } as any);
25
+
26
+ const params = {
27
+ clientId,
28
+ startDate: "2026-05-01T00:00:00.000Z",
29
+ endDate: "2026-05-31T23:59:59.999Z",
30
+ };
31
+
32
+ const result = await getDashboardStats(params);
33
+
34
+ expect(result).toBeDefined();
35
+ expect(result.kpis.totalCalls).toBe(0);
36
+ expect(result.kpis.completedCount).toBe(0);
37
+ expect(result.kpis.avgDurationSeconds).toBe(0);
38
+ expect(result.kpis.inCallCount).toBe(0);
39
+ expect(result.kpis.timeSavedMinutes).toBe(0);
40
+ // ~30 day range -> daily buckets, gap-filled with zeros across the whole range
41
+ expect(result.charts.volumeGranularity).toBe("day");
42
+ expect(result.charts.volumeData.length).toBeGreaterThan(0);
43
+ expect(result.charts.volumeData.every((d) => d.completed === 0)).toBe(true);
44
+ expect(result.charts.volumeData[0].date).toBe("2026-05-01");
45
+ expect(
46
+ result.charts.volumeData[result.charts.volumeData.length - 1].date,
47
+ ).toBe("2026-05-31");
48
+ });
49
+
50
+ it("should pick a bucket size that keeps the volume chart readable across range lengths", async () => {
51
+ const clientId = "client-dash-456";
52
+
53
+ await getClientsConfigCollection().insertOne({
54
+ clientId,
55
+ timezone: "UTC",
56
+ products: {},
57
+ } as any);
58
+
59
+ const oneDay = await getDashboardStats({
60
+ clientId,
61
+ startDate: "2026-05-01T00:00:00.000Z",
62
+ endDate: "2026-05-01T23:59:59.999Z",
63
+ });
64
+ expect(oneDay.charts.volumeGranularity).toBe("hour");
65
+
66
+ const threeWeeks = await getDashboardStats({
67
+ clientId,
68
+ startDate: "2026-05-01T00:00:00.000Z",
69
+ endDate: "2026-05-21T23:59:59.999Z",
70
+ });
71
+ expect(threeWeeks.charts.volumeGranularity).toBe("day");
72
+
73
+ const fourMonths = await getDashboardStats({
74
+ clientId,
75
+ startDate: "2026-01-01T00:00:00.000Z",
76
+ endDate: "2026-04-30T23:59:59.999Z",
77
+ });
78
+ expect(fourMonths.charts.volumeGranularity).toBe("week");
79
+
80
+ const eighteenMonths = await getDashboardStats({
81
+ clientId,
82
+ startDate: "2025-01-01T00:00:00.000Z",
83
+ endDate: "2026-06-30T23:59:59.999Z",
84
+ });
85
+ expect(eighteenMonths.charts.volumeGranularity).toBe("month");
86
+
87
+ const threeYears = await getDashboardStats({
88
+ clientId,
89
+ startDate: "2023-01-01T00:00:00.000Z",
90
+ endDate: "2026-01-01T23:59:59.999Z",
91
+ });
92
+ expect(threeYears.charts.volumeGranularity).toBe("quarter");
93
+
94
+ const fiveYears = await getDashboardStats({
95
+ clientId,
96
+ startDate: "2021-01-01T00:00:00.000Z",
97
+ endDate: "2026-01-01T23:59:59.999Z",
98
+ });
99
+ expect(fiveYears.charts.volumeGranularity).toBe("year");
100
+ });
101
+
102
+ it("should fill gaps with zero-count buckets so the chart has no missing points", async () => {
103
+ const clientId = "client-dash-789";
104
+
105
+ await getClientsConfigCollection().insertOne({
106
+ clientId,
107
+ timezone: "UTC",
108
+ products: {},
109
+ } as any);
110
+
111
+ // Sparse data: only two of the day's hours have calls.
112
+ await getCallsCollection().insertMany([
113
+ {
114
+ clientId,
115
+ status: "completed",
116
+ callLength: 60,
117
+ createdAt: new Date("2026-05-01T09:15:00Z"),
118
+ },
119
+ {
120
+ clientId,
121
+ status: "completed",
122
+ callLength: 90,
123
+ createdAt: new Date("2026-05-01T18:45:00Z"),
124
+ },
125
+ ] as any);
126
+
127
+ const result = await getDashboardStats({
128
+ clientId,
129
+ startDate: "2026-05-01T00:00:00.000Z",
130
+ endDate: "2026-05-01T23:59:59.999Z",
131
+ });
132
+
133
+ expect(result.charts.volumeGranularity).toBe("hour");
134
+ // A full day of hourly buckets, with no gaps, sorted chronologically.
135
+ expect(result.charts.volumeData).toHaveLength(24);
136
+ expect(result.charts.volumeData[0].date).toBe("2026-05-01T00:00");
137
+ expect(result.charts.volumeData[23].date).toBe("2026-05-01T23:00");
138
+
139
+ const populated = result.charts.volumeData.filter((d) => d.completed > 0);
140
+ expect(populated).toHaveLength(2);
141
+ expect(populated.map((d) => d.date)).toEqual([
142
+ "2026-05-01T09:00",
143
+ "2026-05-01T18:00",
144
+ ]);
145
+
146
+ const empty = result.charts.volumeData.filter((d) => d.completed === 0);
147
+ expect(empty).toHaveLength(22);
148
+ });
149
+
150
+ it("should count in-progress and answered as inCallCount, not completed", async () => {
151
+ const clientId = "client-dash-in-call";
152
+
153
+ await getClientsConfigCollection().insertOne({
154
+ clientId,
155
+ timezone: "UTC",
156
+ products: {},
157
+ } as any);
158
+
159
+ await getCallsCollection().insertMany([
160
+ {
161
+ clientId,
162
+ status: "in-progress",
163
+ callLength: 30,
164
+ createdAt: new Date("2026-05-10T10:00:00Z"),
165
+ },
166
+ {
167
+ clientId,
168
+ status: "answered",
169
+ callLength: 45,
170
+ createdAt: new Date("2026-05-10T11:00:00Z"),
171
+ },
172
+ {
173
+ clientId,
174
+ status: "completed",
175
+ callLength: 120,
176
+ createdAt: new Date("2026-05-10T12:00:00Z"),
177
+ },
178
+ ] as any);
179
+
180
+ const result = await getDashboardStats({
181
+ clientId,
182
+ startDate: "2026-05-01T00:00:00.000Z",
183
+ endDate: "2026-05-31T23:59:59.999Z",
184
+ });
185
+
186
+ expect(result.kpis.inCallCount).toBe(2);
187
+ expect(result.kpis.completedCount).toBe(1);
188
+ expect(result.kpis.totalCalls).toBe(3);
189
+ expect(result.kpis.avgDurationSeconds).toBe(120);
190
+ expect(result.kpis.timeSavedMinutes).toBe(2);
191
+ });
192
+
193
+ it("should not count physical busy status as inCall or answered duration", async () => {
194
+ const clientId = "client-dash-busy";
195
+
196
+ await getClientsConfigCollection().insertOne({
197
+ clientId,
198
+ timezone: "UTC",
199
+ products: {},
200
+ } as any);
201
+
202
+ await getCallsCollection().insertMany([
203
+ {
204
+ clientId,
205
+ status: "busy",
206
+ callLength: 60,
207
+ createdAt: new Date("2026-05-10T10:00:00Z"),
208
+ },
209
+ {
210
+ clientId,
211
+ status: "completed",
212
+ callLength: 90,
213
+ createdAt: new Date("2026-05-10T11:00:00Z"),
214
+ },
215
+ ] as any);
216
+
217
+ const result = await getDashboardStats({
218
+ clientId,
219
+ startDate: "2026-05-01T00:00:00.000Z",
220
+ endDate: "2026-05-31T23:59:59.999Z",
221
+ });
222
+
223
+ expect(result.kpis.inCallCount).toBe(0);
224
+ expect(result.kpis.completedCount).toBe(1);
225
+ expect(result.kpis.totalCalls).toBe(1);
226
+ expect(result.kpis.avgDurationSeconds).toBe(90);
227
+ expect(result.kpis.timeSavedMinutes).toBe(2);
228
+ });
229
+ });