@talkpilot/core-db 1.2.0 → 1.2.2

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 (168) hide show
  1. package/README.md +95 -116
  2. package/README_OLD.md +160 -0
  3. package/dist/talkpilot/calls/calls.dashboard.d.ts +3 -0
  4. package/dist/talkpilot/calls/calls.dashboard.d.ts.map +1 -0
  5. package/dist/talkpilot/calls/calls.dashboard.js +191 -0
  6. package/dist/talkpilot/calls/calls.dashboard.js.map +1 -0
  7. package/dist/talkpilot/calls/calls.getters.d.ts +3 -3
  8. package/dist/talkpilot/calls/calls.getters.d.ts.map +1 -1
  9. package/dist/talkpilot/calls/calls.getters.js +1 -178
  10. package/dist/talkpilot/calls/calls.getters.js.map +1 -1
  11. package/dist/talkpilot/calls/calls.types.d.ts +3 -55
  12. package/dist/talkpilot/calls/calls.types.d.ts.map +1 -1
  13. package/dist/talkpilot/calls/dashboard/calls.dashboard.d.ts +36 -0
  14. package/dist/talkpilot/calls/dashboard/calls.dashboard.d.ts.map +1 -0
  15. package/dist/talkpilot/calls/dashboard/calls.dashboard.js +208 -0
  16. package/dist/talkpilot/calls/dashboard/calls.dashboard.js.map +1 -0
  17. package/dist/talkpilot/calls/dashboard/calls.dashboard.types.d.ts +66 -0
  18. package/dist/talkpilot/calls/dashboard/calls.dashboard.types.d.ts.map +1 -0
  19. package/dist/talkpilot/calls/dashboard/calls.dashboard.types.js +3 -0
  20. package/dist/talkpilot/calls/dashboard/calls.dashboard.types.js.map +1 -0
  21. package/dist/talkpilot/calls/index.d.ts +1 -0
  22. package/dist/talkpilot/calls/index.d.ts.map +1 -1
  23. package/dist/talkpilot/calls/index.js +1 -0
  24. package/dist/talkpilot/calls/index.js.map +1 -1
  25. package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts +2 -2
  26. package/dist/talkpilot/clientsConfig/clientsConfig.getters.d.ts.map +1 -1
  27. package/dist/talkpilot/clientsConfig/clientsConfig.getters.js +11 -10
  28. package/dist/talkpilot/clientsConfig/clientsConfig.getters.js.map +1 -1
  29. package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts +20 -9
  30. package/dist/talkpilot/clientsConfig/clientsConfig.types.d.ts.map +1 -1
  31. package/dist/talkpilot/clientsConfig/clientsConfig.types.js +6 -0
  32. package/dist/talkpilot/clientsConfig/clientsConfig.types.js.map +1 -1
  33. package/dist/talkpilot/flows/flows.schema.js +1 -1
  34. package/dist/talkpilot/phone_numbers/index.d.ts +2 -2
  35. package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts +1 -1
  36. package/dist/talkpilot/phone_numbers/phone_numbers.getter.d.ts.map +1 -1
  37. package/dist/talkpilot/phone_numbers/phone_numbers.getter.js +5 -3
  38. package/dist/talkpilot/phone_numbers/phone_numbers.getter.js.map +1 -1
  39. package/dist/talkpilot/phone_numbers/phone_numbers.schema.js +12 -12
  40. package/dist/talkpilot/phone_numbers/phone_numbers.types.d.ts +4 -4
  41. package/dist/talkpilot/results/results.getter.d.ts.map +1 -1
  42. package/dist/talkpilot/results/results.getter.js.map +1 -1
  43. package/dist/talkpilot/retry_analyze/retryAnalyze.getters.d.ts.map +1 -1
  44. package/dist/talkpilot/retry_analyze/retryAnalyze.getters.js.map +1 -1
  45. package/dist/utils/shared.types.d.ts +5 -0
  46. package/dist/utils/shared.types.d.ts.map +1 -0
  47. package/dist/utils/shared.types.js +3 -0
  48. package/dist/utils/shared.types.js.map +1 -0
  49. package/package.json +2 -1
  50. package/src/__tests__/setup.ts +20 -20
  51. package/src/connection.ts +42 -42
  52. package/src/index.ts +16 -16
  53. package/src/municipal/__tests__/validation.spec.ts +62 -62
  54. package/src/municipal/cities/cities.getters.ts +50 -50
  55. package/src/municipal/cities/cities.types.ts +11 -11
  56. package/src/municipal/cities/index.ts +2 -2
  57. package/src/municipal/departmentsSubjects/departmentsSubjects.getters.ts +282 -282
  58. package/src/municipal/departmentsSubjects/departmentsSubjects.types.ts +72 -72
  59. package/src/municipal/departmentsSubjects/index.ts +9 -9
  60. package/src/municipal/index.ts +21 -21
  61. package/src/municipal/mongodb-client.ts +61 -61
  62. package/src/municipal/streets/index.ts +2 -2
  63. package/src/municipal/streets/streets.getters.ts +125 -125
  64. package/src/municipal/streets/streets.types.ts +18 -18
  65. package/src/municipal/systemInstructions/__tests__/getters.spec.ts +113 -113
  66. package/src/municipal/systemInstructions/__tests__/setters.spec.ts +274 -274
  67. package/src/municipal/systemInstructions/index.ts +7 -7
  68. package/src/municipal/systemInstructions/instructions.getters.ts +57 -57
  69. package/src/municipal/systemInstructions/instructions.setters.ts +119 -119
  70. package/src/municipal/systemInstructions/instructions.types.ts +30 -30
  71. package/src/municipal/tickets/__tests__/tickets.getters.spec.ts +66 -66
  72. package/src/municipal/tickets/index.ts +2 -2
  73. package/src/municipal/tickets/tickets.getters.ts +261 -261
  74. package/src/municipal/tickets/tickets.types.ts +43 -43
  75. package/src/municipal/utils/types.ts +11 -11
  76. package/src/talkpilot/__tests__/db.spec.ts +38 -38
  77. package/src/talkpilot/__tests__/mongodb-client.spec.ts +18 -18
  78. package/src/talkpilot/__tests__/validation.spec.ts +68 -68
  79. package/src/talkpilot/agents/__tests__/agents.getters.spec.ts +29 -29
  80. package/src/talkpilot/agents/agents.getters.ts +34 -34
  81. package/src/talkpilot/agents/agents.types.ts +14 -14
  82. package/src/talkpilot/agents/index.ts +2 -2
  83. package/src/talkpilot/backgroundToolResults/__tests__/backgroundToolResults.getters.spec.ts +147 -147
  84. package/src/talkpilot/backgroundToolResults/backgroundToolResults.getters.ts +65 -65
  85. package/src/talkpilot/backgroundToolResults/backgroundToolResults.types.ts +23 -23
  86. package/src/talkpilot/backgroundToolResults/index.ts +2 -2
  87. package/src/talkpilot/calls/__tests__/callStats.utils.spec.ts +128 -128
  88. package/src/talkpilot/calls/__tests__/calls.dashboard.spec.ts +46 -0
  89. package/src/talkpilot/calls/__tests__/calls.spec.ts +270 -252
  90. package/src/talkpilot/calls/calls.getters.ts +248 -446
  91. package/src/talkpilot/calls/calls.types.ts +113 -171
  92. package/src/talkpilot/calls/dashboard/calls.dashboard.ts +243 -0
  93. package/src/talkpilot/calls/dashboard/calls.dashboard.types.ts +70 -0
  94. package/src/talkpilot/calls/index.ts +3 -2
  95. package/src/talkpilot/clientAudioBuffers/__tests__/clientAudioBuffer.getters.spec.ts +160 -160
  96. package/src/talkpilot/clientAudioBuffers/clientAudioBuffer.getters.ts +117 -117
  97. package/src/talkpilot/clientAudioBuffers/clientsAudioBuffers.types.ts +25 -25
  98. package/src/talkpilot/clientAudioBuffers/index.ts +2 -2
  99. package/src/talkpilot/clients/clients.getters.ts +16 -16
  100. package/src/talkpilot/clients/clients.types.ts +14 -14
  101. package/src/talkpilot/clients/index.ts +2 -2
  102. package/src/talkpilot/clientsConfig/__tests__/clientsConfig.getters.spec.ts +53 -0
  103. package/src/talkpilot/clientsConfig/__tests__/clientsConfig.spec.ts +197 -106
  104. package/src/talkpilot/clientsConfig/clientsConfig.getters.ts +55 -44
  105. package/src/talkpilot/clientsConfig/clientsConfig.types.ts +127 -94
  106. package/src/talkpilot/clientsConfig/index.ts +2 -2
  107. package/src/talkpilot/flows/__tests__/flows.schema.spec.ts +71 -67
  108. package/src/talkpilot/flows/flows.getter.ts +14 -14
  109. package/src/talkpilot/flows/flows.schema.ts +153 -153
  110. package/src/talkpilot/flows/flows.types.ts +184 -184
  111. package/src/talkpilot/flows/index.ts +2 -2
  112. package/src/talkpilot/groups/__tests__/groups.spec.ts +90 -90
  113. package/src/talkpilot/groups/__tests__/phone.utils.spec.ts +32 -32
  114. package/src/talkpilot/groups/groups.getters.ts +30 -30
  115. package/src/talkpilot/groups/groups.types.ts +29 -29
  116. package/src/talkpilot/groups/index.ts +3 -3
  117. package/src/talkpilot/groups/phone.utils.ts +46 -46
  118. package/src/talkpilot/index.ts +29 -29
  119. package/src/talkpilot/leads/index.ts +2 -2
  120. package/src/talkpilot/leads/leads.getter.ts +6 -6
  121. package/src/talkpilot/leads/leads.schema.ts +33 -33
  122. package/src/talkpilot/leads/leads.types.ts +20 -20
  123. package/src/talkpilot/mongodb-client.ts +78 -78
  124. package/src/talkpilot/phone_numbers/__tests__/phone_numbers.spec.ts +252 -247
  125. package/src/talkpilot/phone_numbers/index.ts +2 -2
  126. package/src/talkpilot/phone_numbers/phone_numbers.getter.ts +158 -154
  127. package/src/talkpilot/phone_numbers/phone_numbers.schema.ts +17 -17
  128. package/src/talkpilot/phone_numbers/phone_numbers.types.ts +30 -30
  129. package/src/talkpilot/plans/__tests__/plans.spec.ts +70 -70
  130. package/src/talkpilot/plans/index.ts +2 -2
  131. package/src/talkpilot/plans/plans.getters.ts +132 -132
  132. package/src/talkpilot/plans/plans.types.ts +89 -89
  133. package/src/talkpilot/results/index.ts +7 -7
  134. package/src/talkpilot/results/results.getter.ts +39 -35
  135. package/src/talkpilot/results/results.schema.ts +25 -25
  136. package/src/talkpilot/results/results.types.ts +34 -34
  137. package/src/talkpilot/retry_analyze/__tests__/retryAnalyze.getters.spec.ts +156 -156
  138. package/src/talkpilot/retry_analyze/index.ts +2 -2
  139. package/src/talkpilot/retry_analyze/retryAnalyze.getters.ts +84 -75
  140. package/src/talkpilot/retry_analyze/retryAnalyze.types.ts +13 -13
  141. package/src/talkpilot/sessions/__tests__/sessions.spec.ts +147 -147
  142. package/src/talkpilot/sessions/index.ts +2 -2
  143. package/src/talkpilot/sessions/sessions.getter.ts +92 -92
  144. package/src/talkpilot/sessions/sessions.schema.ts +34 -34
  145. package/src/talkpilot/sessions/sessions.types.ts +30 -30
  146. package/src/talkpilot/subscriptions/__tests__/subscriptions.getters.utils.spec.ts +45 -45
  147. package/src/talkpilot/subscriptions/index.ts +3 -3
  148. package/src/talkpilot/subscriptions/subscriptions.getters.ts +146 -146
  149. package/src/talkpilot/subscriptions/subscriptions.getters.utils.ts +33 -33
  150. package/src/talkpilot/subscriptions/subscriptions.types.ts +66 -66
  151. package/src/talkpilot/utils/__tests__/query.utils.spec.ts +49 -49
  152. package/src/talkpilot/utils/query.utils.ts +21 -21
  153. package/src/test-utils/db-utils.ts +24 -24
  154. package/src/test-utils/factories/index.ts +12 -12
  155. package/src/test-utils/factories/municipal/cities.ts +16 -16
  156. package/src/test-utils/factories/municipal/departmentsSubjects.ts +37 -37
  157. package/src/test-utils/factories/municipal/streets.ts +22 -22
  158. package/src/test-utils/factories/municipal/tickets.ts +39 -39
  159. package/src/test-utils/factories/talkpilot/agents.ts +19 -19
  160. package/src/test-utils/factories/talkpilot/calls.ts +37 -37
  161. package/src/test-utils/factories/talkpilot/clientAudioBuffers.ts +20 -20
  162. package/src/test-utils/factories/talkpilot/clientsConfig.ts +18 -18
  163. package/src/test-utils/factories/talkpilot/flows.ts +33 -33
  164. package/src/test-utils/factories/talkpilot/groups.ts +33 -33
  165. package/src/test-utils/factories/talkpilot/phone_numbers.ts +22 -22
  166. package/src/test-utils/factories/talkpilot/sessions.ts +35 -35
  167. package/src/utils/shared.types.ts +4 -0
  168. package/src/utils/validation.ts +23 -23
@@ -1,171 +1,113 @@
1
- import { ObjectId, Sort, WithId } from "mongodb";
2
- import { TranscriptionSegment } from "../results";
3
-
4
- export const CONFERENCE_ROLE_CUSTOMER = "customer" as const;
5
- export const CONFERENCE_ROLE_SUPERVISOR = "supervisor" as const;
6
-
7
- export type ConferenceRole =
8
- | typeof CONFERENCE_ROLE_CUSTOMER
9
- | typeof CONFERENCE_ROLE_SUPERVISOR
10
- | null;
11
-
12
- export type Call = {
13
- callSid: string;
14
- flowId: ObjectId;
15
- clientId: string;
16
- sessionId: ObjectId | null;
17
- runId?: string;
18
- resultId: ObjectId;
19
- customerPhoneNumber: string;
20
- agentPhoneNumber: string;
21
- isIncomingCall: boolean;
22
- isOutgoingCall: boolean;
23
- callLength: number;
24
- transcription?: TranscriptionSegment[];
25
- status?: string;
26
- leads?: Record<string, string>[];
27
- summary?: string;
28
- recordingUrl?: string;
29
- env: string;
30
- updatedAt: Date;
31
- createdAt: Date;
32
- isAnsweredByAnsweringMachine?: boolean;
33
- agentHungUp?: boolean;
34
- endReason?: string;
35
- conferenceName?: string | null;
36
- conferenceSid?: string | null;
37
- conferenceParticipantSid?: string | null;
38
- isConferenceCall?: boolean;
39
- conferenceRole?: ConferenceRole;
40
- redirectedCall?: boolean;
41
- toolExecutions?: ToolExecution[];
42
- };
43
-
44
- export type ToolExecution = {
45
- toolName: string;
46
- executedAt: Date;
47
- durationMs: number;
48
- args: Record<string, unknown> | { _redacted: true };
49
- meta: ToolExecutionMeta;
50
- status: 'success' | 'error';
51
- httpStatus?: number;
52
- response?: Record<string, unknown>;
53
- };
54
-
55
- 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' };
65
-
66
- export type CallQueryOptions = {
67
- sort?: Sort;
68
- skip?: number;
69
- limit?: number;
70
- };
71
-
72
- export type CallsFilterParams = {
73
- clientId: string;
74
- callSid?: string;
75
- startDate?: Date;
76
- endDate?: Date;
77
- status?: string;
78
- customerPhoneNumber?: string;
79
- agentPhoneNumber?: string;
80
- flowId?: string;
81
- runId?: string;
82
- sessionId?: string;
83
- isIncoming?: boolean;
84
- search?: string;
85
- };
86
-
87
- export type ImmutableCallFields =
88
- | "customerPhoneNumber"
89
- | "agentPhoneNumber"
90
- | "isIncomingCall"
91
- | "isOutgoingCall"
92
- | "sessionId"
93
- | "callSid"
94
- | "flowId"
95
- | "clientId"
96
- | "runId"
97
- | "resultId"
98
- | "env"
99
- | "createdAt";
100
-
101
- export type CallUpdateParams = Partial<Omit<Call, ImmutableCallFields>>;
102
-
103
- export type CallDoc = WithId<Call>;
104
-
105
- export type CallsByHour = { hour: string; calls: number };
106
-
107
- export type CountOpts = {
108
- isOutgoingCall?: boolean;
109
- isIncomingCall?: boolean;
110
- };
111
-
112
- export type DateRange = {
113
- since?: Date;
114
- until?: Date;
115
- };
116
-
117
- export type HourlyBucket = {
118
- hour: string;
119
- calls: number;
120
- }
121
-
122
- export type DailyBucket = {
123
- date: string;
124
- count: number;
125
- completed: number;
126
- }
127
-
128
- export type CallLengthBuckets = {
129
- short: number;
130
- medium: number;
131
- long: number;
132
- }
133
-
134
- export type DashboardKpis = {
135
- totalCalls: number;
136
- avgDurationSeconds: number;
137
- timeSavedMinutes: number;
138
- successRate: number;
139
- completedCount: number;
140
- failedCount: number;
141
- noAnswerCount: number;
142
- busyCount: number;
143
- }
144
-
145
- export type DashboardCharts = {
146
- volumeData: DailyBucket[];
147
- heatmap: Record<string, HourlyBucket[]>;
148
- callLengthBuckets: CallLengthBuckets;
149
- }
150
-
151
- export type DashboardStatsParams = {
152
- clientId: string;
153
- startDate: string;
154
- endDate: string;
155
- }
156
-
157
- export type RecentCall = {
158
- callSid: string;
159
- customerPhoneNumber: string;
160
- status: string | undefined;
161
- callLength: number;
162
- createdAt: Date;
163
- summary: string | undefined;
164
- isIncomingCall: boolean;
165
- }
166
-
167
- export type DashboardStatsResult = {
168
- kpis: DashboardKpis;
169
- charts: DashboardCharts;
170
- recentCalls: RecentCall[];
171
- }
1
+ import { ObjectId, Sort, WithId } from "mongodb";
2
+ import { TranscriptionSegment } from "../results";
3
+
4
+ export const CONFERENCE_ROLE_CUSTOMER = "customer" as const;
5
+ export const CONFERENCE_ROLE_SUPERVISOR = "supervisor" as const;
6
+
7
+ export type ConferenceRole =
8
+ | typeof CONFERENCE_ROLE_CUSTOMER
9
+ | typeof CONFERENCE_ROLE_SUPERVISOR
10
+ | null;
11
+
12
+ export type Call = {
13
+ callSid: string;
14
+ flowId: ObjectId;
15
+ clientId: string;
16
+ sessionId: ObjectId | null;
17
+ runId?: string;
18
+ resultId: ObjectId;
19
+ customerPhoneNumber: string;
20
+ agentPhoneNumber: string;
21
+ isIncomingCall: boolean;
22
+ isOutgoingCall: boolean;
23
+ callLength: number;
24
+ transcription?: TranscriptionSegment[];
25
+ status?: string;
26
+ leads?: Record<string, string>[];
27
+ summary?: string;
28
+ recordingUrl?: string;
29
+ env: string;
30
+ updatedAt: Date;
31
+ createdAt: Date;
32
+ isAnsweredByAnsweringMachine?: boolean;
33
+ agentHungUp?: boolean;
34
+ endReason?: string;
35
+ conferenceName?: string | null;
36
+ conferenceSid?: string | null;
37
+ conferenceParticipantSid?: string | null;
38
+ isConferenceCall?: boolean;
39
+ conferenceRole?: ConferenceRole;
40
+ redirectedCall?: boolean;
41
+ toolExecutions?: ToolExecution[];
42
+ };
43
+
44
+ export type ToolExecution = {
45
+ toolName: string;
46
+ executedAt: Date;
47
+ durationMs: number;
48
+ args: Record<string, unknown> | { _redacted: true };
49
+ meta: ToolExecutionMeta;
50
+ status: "success" | "error";
51
+ httpStatus?: number;
52
+ response?: Record<string, unknown>;
53
+ };
54
+
55
+ 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" };
65
+
66
+ export type CallQueryOptions = {
67
+ sort?: Sort;
68
+ skip?: number;
69
+ limit?: number;
70
+ };
71
+
72
+ export type CallsFilterParams = {
73
+ clientId: string;
74
+ callSid?: string;
75
+ startDate?: Date;
76
+ endDate?: Date;
77
+ status?: string;
78
+ customerPhoneNumber?: string;
79
+ agentPhoneNumber?: string;
80
+ flowId?: string;
81
+ runId?: string;
82
+ sessionId?: string;
83
+ isIncoming?: boolean;
84
+ search?: string;
85
+ };
86
+
87
+ export type ImmutableCallFields =
88
+ | "customerPhoneNumber"
89
+ | "agentPhoneNumber"
90
+ | "isIncomingCall"
91
+ | "isOutgoingCall"
92
+ | "sessionId"
93
+ | "callSid"
94
+ | "flowId"
95
+ | "clientId"
96
+ | "runId"
97
+ | "resultId"
98
+ | "env"
99
+ | "createdAt";
100
+
101
+ export type CallUpdateParams = Partial<Omit<Call, ImmutableCallFields>>;
102
+
103
+ export type CallDoc = WithId<Call>;
104
+
105
+ export type CountOpts = {
106
+ isOutgoingCall?: boolean;
107
+ isIncomingCall?: boolean;
108
+ };
109
+
110
+ export type DateRange = {
111
+ since?: Date;
112
+ until?: Date;
113
+ };
@@ -0,0 +1,243 @@
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 {
7
+ DashboardAggregationResult,
8
+ DashboardDailyTrendMetric,
9
+ DashboardHeatmapMetric,
10
+ DashboardReportQuery,
11
+ DashboardReportResponse,
12
+ DashboardSummaryMetrics,
13
+ RawDailyAggregationResult,
14
+ RawHourlyAggregationResult,
15
+ } from "./calls.dashboard.types";
16
+ import { CallLengthThresholds } from "src/utils/shared.types";
17
+
18
+ const DEFAULT_KPI_DATA = {
19
+ totalCalls: 0,
20
+ totalDuration: 0,
21
+ completedCount: 0,
22
+ failedCount: 0,
23
+ noAnswerCount: 0,
24
+ busyCount: 0,
25
+ };
26
+
27
+ dayjs.extend(utc);
28
+ dayjs.extend(timezonePlugin);
29
+
30
+ function buildKpisPipeline() {
31
+ return [
32
+ {
33
+ $group: {
34
+ _id: null,
35
+ totalCalls: { $sum: 1 },
36
+ totalDuration: { $sum: "$callLength" },
37
+ completedCount: {
38
+ $sum: { $cond: [{ $eq: ["$status", "completed"] }, 1, 0] },
39
+ },
40
+ failedCount: {
41
+ $sum: { $cond: [{ $eq: ["$status", "failed"] }, 1, 0] },
42
+ },
43
+ noAnswerCount: {
44
+ $sum: { $cond: [{ $eq: ["$status", "no-answer"] }, 1, 0] },
45
+ },
46
+ busyCount: {
47
+ $sum: { $cond: [{ $eq: ["$status", "busy"] }, 1, 0] },
48
+ },
49
+ },
50
+ },
51
+ ];
52
+ }
53
+
54
+ function buildDailyDataPipeline(timezone: string) {
55
+ return [
56
+ {
57
+ $group: {
58
+ _id: {
59
+ $dateToString: {
60
+ format: "%Y-%m-%d",
61
+ date: "$createdAt",
62
+ timezone: timezone,
63
+ },
64
+ },
65
+ count: { $sum: 1 },
66
+ completed: {
67
+ $sum: { $cond: [{ $eq: ["$status", "completed"] }, 1, 0] },
68
+ },
69
+ },
70
+ },
71
+ { $sort: { _id: 1 } },
72
+ ];
73
+ }
74
+
75
+ function buildHourlyDataPipeline(timezone: string) {
76
+ return [
77
+ {
78
+ $group: {
79
+ _id: {
80
+ day: {
81
+ $dateToString: {
82
+ format: "%Y-%m-%d",
83
+ date: "$createdAt",
84
+ timezone: timezone,
85
+ },
86
+ },
87
+ hour: { $hour: { date: "$createdAt", timezone: timezone } },
88
+ },
89
+ count: { $sum: 1 },
90
+ },
91
+ },
92
+ ];
93
+ }
94
+
95
+ export function buildCallLengthBucketsPipeline(
96
+ thresholds: CallLengthThresholds,
97
+ ) {
98
+ const { shortThreshold, mediumThreshold } = thresholds;
99
+
100
+ return [
101
+ {
102
+ $group: {
103
+ _id: null,
104
+ short: {
105
+ $sum: { $cond: [{ $lt: ["$callLength", shortThreshold] }, 1, 0] },
106
+ },
107
+ medium: {
108
+ $sum: {
109
+ $cond: [
110
+ {
111
+ $and: [
112
+ { $gte: ["$callLength", shortThreshold] },
113
+ { $lte: ["$callLength", mediumThreshold] },
114
+ ],
115
+ },
116
+ 1,
117
+ 0,
118
+ ],
119
+ },
120
+ },
121
+ long: {
122
+ $sum: { $cond: [{ $gt: ["$callLength", mediumThreshold] }, 1, 0] },
123
+ },
124
+ },
125
+ },
126
+ ];
127
+ }
128
+
129
+ function buildHeatmapData(
130
+ hourlyDataRaw: RawHourlyAggregationResult[],
131
+ ): Record<string, DashboardHeatmapMetric[]> {
132
+ const heatmapMap = new Map<string, Map<number, number>>();
133
+
134
+ for (const bucket of hourlyDataRaw) {
135
+ const dayKey = bucket._id.day;
136
+ const hour = bucket._id.hour;
137
+ if (!heatmapMap.has(dayKey)) heatmapMap.set(dayKey, new Map());
138
+ heatmapMap.get(dayKey)!.set(hour, bucket.count);
139
+ }
140
+
141
+ const toSortedBuckets = (
142
+ map: Map<number, number>,
143
+ ): DashboardHeatmapMetric[] =>
144
+ Array.from(map.entries())
145
+ .sort(([a], [b]) => a - b)
146
+ .map(([h, c]) => ({
147
+ hour: h,
148
+ calls: c,
149
+ }));
150
+
151
+ const heatmap: Record<string, DashboardHeatmapMetric[]> = {};
152
+ for (const [day, hourMap] of Array.from(heatmapMap.entries()).sort()) {
153
+ heatmap[day] = toSortedBuckets(hourMap);
154
+ }
155
+
156
+ return heatmap;
157
+ }
158
+
159
+ export async function getDashboardStats(
160
+ params: DashboardReportQuery,
161
+ ): Promise<DashboardReportResponse> {
162
+ const { clientId, startDate, endDate } = params;
163
+ const clientConfig = await getClientConfig(clientId);
164
+ const timezone = clientConfig?.timezone ?? "UTC";
165
+
166
+ const startDateObj = dayjs.tz(startDate, timezone).startOf("day").toDate();
167
+ const endDateObj = dayjs.tz(endDate, timezone).endOf("day").toDate();
168
+
169
+ const thresholds: CallLengthThresholds = {
170
+ shortThreshold: clientConfig?.callLengthThresholds?.shortThreshold ?? 60,
171
+ mediumThreshold: clientConfig?.callLengthThresholds?.mediumThreshold ?? 180,
172
+ };
173
+
174
+ const pipeline = [
175
+ {
176
+ $match: {
177
+ clientId,
178
+ createdAt: { $gte: startDateObj, $lte: endDateObj },
179
+ },
180
+ },
181
+ {
182
+ $facet: {
183
+ kpis: buildKpisPipeline(),
184
+ dailyData: buildDailyDataPipeline(timezone),
185
+ hourlyData: buildHourlyDataPipeline(timezone),
186
+ callLengthBuckets: buildCallLengthBucketsPipeline(thresholds),
187
+ },
188
+ },
189
+ ];
190
+
191
+ const callsCollection = getCallsCollection();
192
+ const [aggregatedResult] = await callsCollection
193
+ .aggregate<DashboardAggregationResult>(pipeline)
194
+ .toArray();
195
+
196
+ const kpiData = aggregatedResult?.kpis?.[0] ?? DEFAULT_KPI_DATA;
197
+ const dailyDataRaw = aggregatedResult?.dailyData ?? [];
198
+ const hourlyDataRaw = aggregatedResult?.hourlyData ?? [];
199
+ const callLengthRaw = aggregatedResult?.callLengthBuckets?.[0] ?? {
200
+ short: 0,
201
+ medium: 0,
202
+ long: 0,
203
+ };
204
+
205
+ const kpis: DashboardSummaryMetrics = {
206
+ totalCalls: kpiData.totalCalls,
207
+ avgDurationSeconds:
208
+ kpiData.totalCalls > 0
209
+ ? Math.round((kpiData.totalDuration ?? 0) / kpiData.totalCalls)
210
+ : 0,
211
+ timeSavedMinutes: Math.round((kpiData.totalDuration ?? 0) / 60),
212
+ successRate:
213
+ kpiData.totalCalls > 0
214
+ ? Math.round((kpiData.completedCount / kpiData.totalCalls) * 1000) / 10
215
+ : 0,
216
+ completedCount: kpiData.completedCount,
217
+ failedCount: kpiData.failedCount,
218
+ noAnswerCount: kpiData.noAnswerCount,
219
+ busyCount: kpiData.busyCount,
220
+ };
221
+
222
+ const volumeData: DashboardDailyTrendMetric[] = dailyDataRaw.map((d) => ({
223
+ date: d._id,
224
+ count: d.count,
225
+ completed: d.completed,
226
+ }));
227
+
228
+ const heatmap = buildHeatmapData(hourlyDataRaw);
229
+
230
+ const response: DashboardReportResponse = {
231
+ kpis,
232
+ charts: {
233
+ volumeData,
234
+ heatmap,
235
+ callLengthBuckets: {
236
+ short: callLengthRaw.short,
237
+ medium: callLengthRaw.medium,
238
+ long: callLengthRaw.long,
239
+ },
240
+ },
241
+ };
242
+ return response;
243
+ }
@@ -0,0 +1,70 @@
1
+ export type DashboardHeatmapMetric = {
2
+ hour: number;
3
+ calls: number;
4
+ };
5
+
6
+ export type DashboardDailyTrendMetric = {
7
+ date: string;
8
+ count: number;
9
+ completed: number;
10
+ };
11
+
12
+ export type DashboardDurationSegments = {
13
+ short: number;
14
+ medium: number;
15
+ long: number;
16
+ };
17
+
18
+ export type DashboardSummaryMetrics = {
19
+ totalCalls: number;
20
+ avgDurationSeconds: number;
21
+ timeSavedMinutes: number;
22
+ successRate: number;
23
+ completedCount: number;
24
+ failedCount: number;
25
+ noAnswerCount: number;
26
+ busyCount: number;
27
+ };
28
+
29
+ export type DashboardVisualData = {
30
+ volumeData: DashboardDailyTrendMetric[];
31
+ heatmap: Record<string, DashboardHeatmapMetric[]>;
32
+ callLengthBuckets: DashboardDurationSegments;
33
+ };
34
+
35
+ export type DashboardReportQuery = {
36
+ clientId: string;
37
+ startDate: string;
38
+ endDate: string;
39
+ };
40
+
41
+ export type DashboardReportResponse = {
42
+ kpis: DashboardSummaryMetrics;
43
+ charts: DashboardVisualData;
44
+ };
45
+ type RawKpiResult = {
46
+ totalCalls: number;
47
+ totalDuration: number | null;
48
+ completedCount: number;
49
+ failedCount: number;
50
+ noAnswerCount: number;
51
+ busyCount: number;
52
+ };
53
+
54
+ export type RawDailyAggregationResult = {
55
+ _id: string;
56
+ count: number;
57
+ completed: number;
58
+ };
59
+
60
+ export type RawHourlyAggregationResult = {
61
+ _id: { day: string; hour: number };
62
+ count: number;
63
+ };
64
+
65
+ export interface DashboardAggregationResult {
66
+ kpis: RawKpiResult[];
67
+ dailyData: RawDailyAggregationResult[];
68
+ hourlyData: RawHourlyAggregationResult[];
69
+ callLengthBuckets: DashboardDurationSegments[];
70
+ }
@@ -1,2 +1,3 @@
1
- export * from "./calls.types";
2
- export * from "./calls.getters";
1
+ export * from "./calls.types";
2
+ export * from "./calls.getters";
3
+ export * from "./dashboard/calls.dashboard";