@itleanchatbot/shared-models-js-postgres 3.1.4 → 3.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
async up(queryInterface) {
|
|
5
|
+
await queryInterface.sequelize.query(`
|
|
6
|
+
DROP VIEW IF EXISTS channel_message_type_daily;
|
|
7
|
+
|
|
8
|
+
CREATE VIEW channel_message_type_daily AS
|
|
9
|
+
WITH base AS (
|
|
10
|
+
SELECT
|
|
11
|
+
((bi."createdAt" AT TIME ZONE 'UTC') AT TIME ZONE 'America/Sao_Paulo')::date AS day,
|
|
12
|
+
bi."EnterpriseId" AS "EnterpriseId",
|
|
13
|
+
s."ChannelId" AS "SessionChannelId",
|
|
14
|
+
|
|
15
|
+
LOWER(
|
|
16
|
+
COALESCE(
|
|
17
|
+
NULLIF(bi.context::jsonb ->> 'channelType', ''),
|
|
18
|
+
NULLIF(bi.context::jsonb #>> '{inbound_message,channel}', ''),
|
|
19
|
+
NULLIF(bi.context::jsonb #>> '{inboundMessage,channel}', ''),
|
|
20
|
+
CASE
|
|
21
|
+
WHEN (bi.payload::jsonb #>> '{channelId}') = 'msteams'
|
|
22
|
+
OR (bi.context::jsonb #>> '{parsedMessage,channel,id}') = 'msteams'
|
|
23
|
+
THEN 'microsoft_teams' ELSE NULL END,
|
|
24
|
+
CASE
|
|
25
|
+
WHEN (bi.context::jsonb ->> 'channel') ILIKE 'whats%'
|
|
26
|
+
OR (bi.context::jsonb ->> 'channelType') ILIKE 'whats%'
|
|
27
|
+
THEN 'whatsapp_gupshup' ELSE NULL END
|
|
28
|
+
)
|
|
29
|
+
) AS inferred_channel_type_raw,
|
|
30
|
+
|
|
31
|
+
COALESCE(
|
|
32
|
+
bi.context::jsonb #>> '{inbound_id}',
|
|
33
|
+
bi.payload::jsonb #>> '{inboundId}',
|
|
34
|
+
bi.context::jsonb #>> '{inbound_message,payload,id}',
|
|
35
|
+
bi.context::jsonb #>> '{inboundMessage,payload,id}',
|
|
36
|
+
bi.context::jsonb #>> '{inbound_message,id}',
|
|
37
|
+
bi.context::jsonb #>> '{inboundMessage,id}',
|
|
38
|
+
bi.payload::jsonb #>> '{transcription,jobId}',
|
|
39
|
+
bi.id::text
|
|
40
|
+
) AS inbound_id,
|
|
41
|
+
|
|
42
|
+
NULLIF(
|
|
43
|
+
COALESCE(
|
|
44
|
+
bi.payload::jsonb #>> '{transcription,jobId}',
|
|
45
|
+
bi.payload::jsonb ->> 'jobId',
|
|
46
|
+
bi.context::jsonb #>> '{inbound_message,payload,transcription,jobId}',
|
|
47
|
+
bi.context::jsonb #>> '{inboundMessage,payload,transcription,jobId}'
|
|
48
|
+
),
|
|
49
|
+
''
|
|
50
|
+
) AS audio_job_id,
|
|
51
|
+
|
|
52
|
+
/*
|
|
53
|
+
Flags explícitas de áudio (sem regex):
|
|
54
|
+
- contentType/mimetype começando com audio/
|
|
55
|
+
- type = 'audio'
|
|
56
|
+
- fallback por extensão usando ILIKE ANY
|
|
57
|
+
*/
|
|
58
|
+
(
|
|
59
|
+
COALESCE(bi.payload::jsonb ->> 'mimetype', '') ILIKE 'audio/%'
|
|
60
|
+
OR COALESCE(bi.payload::jsonb ->> 'contentType', '') ILIKE 'audio/%'
|
|
61
|
+
OR COALESCE(bi.payload::jsonb #>> '{file,contentType}', '') ILIKE 'audio/%'
|
|
62
|
+
OR COALESCE(bi.context::jsonb #>> '{inbound_message,payload,payload,contentType}', '') ILIKE 'audio/%'
|
|
63
|
+
OR COALESCE(bi.context::jsonb #>> '{inboundMessage,payload,payload,contentType}', '') ILIKE 'audio/%'
|
|
64
|
+
OR COALESCE(bi.context::jsonb #>> '{inbound_message,payload,file,contentType}', '') ILIKE 'audio/%'
|
|
65
|
+
OR COALESCE(bi.context::jsonb #>> '{inboundMessage,payload,file,contentType}', '') ILIKE 'audio/%'
|
|
66
|
+
OR COALESCE(bi.context::jsonb #>> '{inbound_message,payload,payload,type}', '') = 'audio'
|
|
67
|
+
OR COALESCE(bi.context::jsonb #>> '{inboundMessage,payload,payload,type}', '') = 'audio'
|
|
68
|
+
OR LOWER(COALESCE(bi.payload::jsonb #>> '{file,name}', '')) ILIKE ANY (ARRAY['%.ogg','%.oga','%.mp3','%.m4a','%.wav','%.opus','%.ogg%','%.oga%','%.mp3%','%.m4a%','%.wav%','%.opus%'])
|
|
69
|
+
OR LOWER(COALESCE(bi.context::jsonb #>> '{inbound_message,payload,file,name}', '')) ILIKE ANY (ARRAY['%.ogg','%.oga','%.mp3','%.m4a','%.wav','%.opus','%.ogg%','%.oga%','%.mp3%','%.m4a%','%.wav%','%.opus%'])
|
|
70
|
+
OR LOWER(COALESCE(bi.context::jsonb #>> '{inboundMessage,payload,file,name}', '')) ILIKE ANY (ARRAY['%.ogg','%.oga','%.mp3','%.m4a','%.wav','%.opus','%.ogg%','%.oga%','%.mp3%','%.m4a%','%.wav%','%.opus%'])
|
|
71
|
+
OR LOWER(COALESCE(bi.payload::jsonb #>> '{file,url}', '')) ILIKE ANY (ARRAY['%.ogg%','%.oga%','%.mp3%','%.m4a%','%.wav%','%.opus%'])
|
|
72
|
+
OR LOWER(COALESCE(bi.context::jsonb #>> '{inbound_message,payload,file,url}', '')) ILIKE ANY (ARRAY['%.ogg%','%.oga%','%.mp3%','%.m4a%','%.wav%','%.opus%'])
|
|
73
|
+
OR LOWER(COALESCE(bi.context::jsonb #>> '{inboundMessage,payload,file,url}', '')) ILIKE ANY (ARRAY['%.ogg%','%.oga%','%.mp3%','%.m4a%','%.wav%','%.opus%'])
|
|
74
|
+
) AS has_explicit_audio,
|
|
75
|
+
|
|
76
|
+
(
|
|
77
|
+
COALESCE(
|
|
78
|
+
bi.payload::jsonb #>> '{transcription,jobId}',
|
|
79
|
+
bi.payload::jsonb ->> 'jobId',
|
|
80
|
+
bi.context::jsonb #>> '{inbound_message,payload,transcription,jobId}',
|
|
81
|
+
bi.context::jsonb #>> '{inboundMessage,payload,transcription,jobId}'
|
|
82
|
+
) IS NOT NULL
|
|
83
|
+
AND COALESCE(
|
|
84
|
+
NULLIF(bi.payload::jsonb #>> '{transcription,jobId}', ''),
|
|
85
|
+
NULLIF(bi.payload::jsonb ->> 'jobId', ''),
|
|
86
|
+
NULLIF(bi.context::jsonb #>> '{inbound_message,payload,transcription,jobId}', ''),
|
|
87
|
+
NULLIF(bi.context::jsonb #>> '{inboundMessage,payload,transcription,jobId}', '')
|
|
88
|
+
) IS NOT NULL
|
|
89
|
+
) AS has_stt_job
|
|
90
|
+
|
|
91
|
+
FROM "BotIterables" bi
|
|
92
|
+
LEFT JOIN "Sessions" s ON s.id = bi."SessionId"
|
|
93
|
+
WHERE bi.action = 'client'
|
|
94
|
+
AND (
|
|
95
|
+
(bi.context::jsonb ? 'inbound_message') OR
|
|
96
|
+
(bi.context::jsonb ? 'inboundMessage') OR
|
|
97
|
+
(bi.context::jsonb ? 'inbound_id') OR
|
|
98
|
+
(bi.payload::jsonb ? 'inboundId')
|
|
99
|
+
)
|
|
100
|
+
),
|
|
101
|
+
|
|
102
|
+
norm AS (
|
|
103
|
+
SELECT
|
|
104
|
+
b.*,
|
|
105
|
+
CASE
|
|
106
|
+
WHEN inferred_channel_type_raw IN ('microsoft_teams','msteams','teams') THEN 'microsoft_teams'
|
|
107
|
+
WHEN inferred_channel_type_raw IN ('webchat','webchat-transcript') THEN 'webchat'
|
|
108
|
+
WHEN inferred_channel_type_raw LIKE 'whatsapp%' THEN 'whatsapp_gupshup'
|
|
109
|
+
ELSE NULL
|
|
110
|
+
END AS "channelFamily"
|
|
111
|
+
FROM base b
|
|
112
|
+
),
|
|
113
|
+
|
|
114
|
+
typed AS (
|
|
115
|
+
SELECT
|
|
116
|
+
n.*,
|
|
117
|
+
CASE
|
|
118
|
+
WHEN n."channelFamily" = 'microsoft_teams' THEN FALSE
|
|
119
|
+
ELSE (n.has_explicit_audio OR (n.has_stt_job AND n."channelFamily" IN ('webchat','whatsapp_gupshup')))
|
|
120
|
+
END AS has_audio_flag
|
|
121
|
+
FROM norm n
|
|
122
|
+
),
|
|
123
|
+
|
|
124
|
+
consolidated AS (
|
|
125
|
+
SELECT
|
|
126
|
+
t.day,
|
|
127
|
+
t."EnterpriseId",
|
|
128
|
+
t."SessionChannelId",
|
|
129
|
+
t."channelFamily",
|
|
130
|
+
FIRST_VALUE(t.inferred_channel_type_raw) OVER w AS rawChannelTypeAny,
|
|
131
|
+
BOOL_OR(t.has_audio_flag) OVER w AS any_audio_flag,
|
|
132
|
+
FIRST_VALUE(t.audio_job_id) OVER w AS audio_job_id_any,
|
|
133
|
+
t.inbound_id
|
|
134
|
+
FROM typed t
|
|
135
|
+
WINDOW w AS (PARTITION BY t.inbound_id, t."SessionChannelId")
|
|
136
|
+
),
|
|
137
|
+
|
|
138
|
+
msgs_final AS (
|
|
139
|
+
SELECT
|
|
140
|
+
c.day,
|
|
141
|
+
c."EnterpriseId",
|
|
142
|
+
c."SessionChannelId",
|
|
143
|
+
c."channelFamily",
|
|
144
|
+
c.rawChannelTypeAny AS "rawChannelName_from_infer",
|
|
145
|
+
c.inbound_id,
|
|
146
|
+
CASE WHEN c.any_audio_flag THEN 'audio' ELSE 'text' END AS "messageType"
|
|
147
|
+
FROM consolidated c
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
SELECT
|
|
151
|
+
m.day,
|
|
152
|
+
m."EnterpriseId",
|
|
153
|
+
COALESCE(ch2.id, m."SessionChannelId") AS "ChannelId",
|
|
154
|
+
CASE m."channelFamily"
|
|
155
|
+
WHEN 'microsoft_teams' THEN 'Microsoft Teams'
|
|
156
|
+
WHEN 'webchat' THEN 'WebChat'
|
|
157
|
+
WHEN 'whatsapp_gupshup' THEN 'WhatsApp'
|
|
158
|
+
ELSE COALESCE(m."rawChannelName_from_infer", ch.name)
|
|
159
|
+
END AS "channelName",
|
|
160
|
+
COALESCE(m."rawChannelName_from_infer", ch.name) AS "rawChannelName",
|
|
161
|
+
m."channelFamily",
|
|
162
|
+
m."messageType",
|
|
163
|
+
COUNT(DISTINCT m.inbound_id) AS total
|
|
164
|
+
FROM msgs_final m
|
|
165
|
+
LEFT JOIN "Channels" ch ON ch.id = m."SessionChannelId"
|
|
166
|
+
LEFT JOIN "Channels" ch2 ON ch2."EnterpriseId" = m."EnterpriseId"
|
|
167
|
+
AND (
|
|
168
|
+
(m."channelFamily" = 'microsoft_teams' AND ch2.name ILIKE '%teams%')
|
|
169
|
+
OR (m."channelFamily" = 'webchat' AND ch2.name ILIKE '%webchat%')
|
|
170
|
+
OR (m."channelFamily" = 'whatsapp_gupshup' AND (ch2.name ILIKE '%whatsapp%' OR ch2.name ILIKE '%gupshup%' OR ch2.name ILIKE '%whats%'))
|
|
171
|
+
)
|
|
172
|
+
GROUP BY
|
|
173
|
+
m.day,
|
|
174
|
+
m."EnterpriseId",
|
|
175
|
+
COALESCE(ch2.id, m."SessionChannelId"),
|
|
176
|
+
CASE m."channelFamily"
|
|
177
|
+
WHEN 'microsoft_teams' THEN 'Microsoft Teams'
|
|
178
|
+
WHEN 'webchat' THEN 'WebChat'
|
|
179
|
+
WHEN 'whatsapp_gupshup' THEN 'WhatsApp'
|
|
180
|
+
ELSE COALESCE(m."rawChannelName_from_infer", ch.name)
|
|
181
|
+
END,
|
|
182
|
+
COALESCE(m."rawChannelName_from_infer", ch.name),
|
|
183
|
+
m."channelFamily",
|
|
184
|
+
m."messageType";
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
`);
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
async down(queryInterface) {
|
|
193
|
+
await queryInterface.sequelize.query(`
|
|
194
|
+
DROP VIEW IF EXISTS channel_message_type_daily;
|
|
195
|
+
|
|
196
|
+
CREATE VIEW channel_message_type_daily AS
|
|
197
|
+
WITH base AS (
|
|
198
|
+
SELECT
|
|
199
|
+
((bi."createdAt" AT TIME ZONE 'UTC') AT TIME ZONE 'America/Sao_Paulo')::date AS day,
|
|
200
|
+
bi."EnterpriseId" AS "EnterpriseId",
|
|
201
|
+
s."ChannelId" AS "ChannelId",
|
|
202
|
+
ch.name AS "channelName",
|
|
203
|
+
COALESCE(
|
|
204
|
+
bi.context::jsonb #>> '{inbound_id}',
|
|
205
|
+
bi.payload::jsonb #>> '{inboundId}',
|
|
206
|
+
bi.context::jsonb #>> '{inbound_message,payload,id}',
|
|
207
|
+
bi.context::jsonb #>> '{inboundMessage,payload,id}',
|
|
208
|
+
bi.context::jsonb #>> '{inbound_message,id}',
|
|
209
|
+
bi.context::jsonb #>> '{inboundMessage,id}',
|
|
210
|
+
bi.payload::jsonb #>> '{transcription,jobId}',
|
|
211
|
+
bi.id::text
|
|
212
|
+
) AS inbound_id,
|
|
213
|
+
CASE WHEN
|
|
214
|
+
(bi.context::jsonb #>> '{inbound_message,payload,payload,type}') = 'audio' OR
|
|
215
|
+
(bi.context::jsonb #>> '{inboundMessage,payload,payload,type}') = 'audio' OR
|
|
216
|
+
(bi.context::jsonb #>> '{inbound_message,payload,payload,contentType}') ILIKE 'audio/%' OR
|
|
217
|
+
(bi.context::jsonb #>> '{inboundMessage,payload,payload,contentType}') ILIKE 'audio/%' OR
|
|
218
|
+
((bi.payload::jsonb ->> 'mimetype') ILIKE 'audio/%')
|
|
219
|
+
THEN 'audio' ELSE 'text' END AS "messageType"
|
|
220
|
+
FROM "BotIterables" bi
|
|
221
|
+
LEFT JOIN "Sessions" s ON s.id = bi."SessionId"
|
|
222
|
+
LEFT JOIN "Channels" ch ON ch.id = s."ChannelId"
|
|
223
|
+
WHERE bi.action = 'client'
|
|
224
|
+
AND (
|
|
225
|
+
(bi.context::jsonb ? 'inbound_message') OR
|
|
226
|
+
(bi.context::jsonb ? 'inboundMessage')
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
SELECT
|
|
230
|
+
day, "EnterpriseId", "ChannelId", "channelName", "messageType",
|
|
231
|
+
COUNT(DISTINCT inbound_id) AS total
|
|
232
|
+
FROM base
|
|
233
|
+
GROUP BY day, "EnterpriseId", "ChannelId", "channelName", "messageType";
|
|
234
|
+
`);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
/** @type {import('sequelize-cli').Migration} */
|
|
3
|
-
module.exports = {
|
|
4
|
-
async up(queryInterface) {
|
|
5
|
-
await queryInterface.sequelize.query(`
|
|
6
|
-
DROP VIEW IF EXISTS channel_message_type_daily;
|
|
7
|
-
CREATE VIEW channel_message_type_daily AS
|
|
8
|
-
WITH base AS (
|
|
9
|
-
SELECT
|
|
10
|
-
/* Ajuste de fuso antes do ::date */
|
|
11
|
-
((bi."createdAt" AT TIME ZONE 'UTC') AT TIME ZONE 'America/Sao_Paulo')::date AS day,
|
|
12
|
-
bi."EnterpriseId" AS "EnterpriseId",
|
|
13
|
-
s."ChannelId" AS "ChannelId",
|
|
14
|
-
ch.name AS "channelName",
|
|
15
|
-
/* Um id “estável” do inbound para evitar duplicatas (quando existir) */
|
|
16
|
-
COALESCE(
|
|
17
|
-
bi.context::jsonb #>> '{inbound_message,payload,id}',
|
|
18
|
-
bi.context::jsonb #>> '{inboundMessage,payload,id}',
|
|
19
|
-
bi.context::jsonb #>> '{inbound_message,id}',
|
|
20
|
-
bi.context::jsonb #>> '{inboundMessage,id}',
|
|
21
|
-
bi.id::text
|
|
22
|
-
) AS inbound_id,
|
|
23
|
-
/* Regra de áudio bem ampla, sempre em jsonb */
|
|
24
|
-
CASE WHEN
|
|
25
|
-
/* type='audio' em variações do payload */
|
|
26
|
-
((bi.context::jsonb #>> '{inbound_message,payload,payload,type}') = 'audio') OR
|
|
27
|
-
((bi.context::jsonb #>> '{inboundMessage,payload,payload,type}') = 'audio') OR
|
|
28
|
-
/* contentType indicando áudio */
|
|
29
|
-
((bi.context::jsonb #>> '{inbound_message,payload,payload,contentType}') ILIKE 'audio/%') OR
|
|
30
|
-
((bi.context::jsonb #>> '{inboundMessage,payload,payload,contentType}') ILIKE 'audio/%') OR
|
|
31
|
-
/* mimetype salvo em payload do BotIterable (ex.: webchat/whats) */
|
|
32
|
-
((bi.payload::jsonb ->> 'mimetype') ILIKE 'audio/%')
|
|
33
|
-
THEN 'audio'
|
|
34
|
-
ELSE 'text'
|
|
35
|
-
END AS "messageType"
|
|
36
|
-
FROM "BotIterables" bi
|
|
37
|
-
LEFT JOIN "Sessions" s ON s.id = bi."SessionId"
|
|
38
|
-
LEFT JOIN "Channels" ch ON ch.id = s."ChannelId"
|
|
39
|
-
WHERE bi.action = 'client'
|
|
40
|
-
AND bi.type = 'message'
|
|
41
|
-
/* Garante que é mensagem de entrada “real”
|
|
42
|
-
(descarta os BotIterables sintéticos do audio-status) */
|
|
43
|
-
AND (
|
|
44
|
-
(bi.context::jsonb ? 'inbound_message')
|
|
45
|
-
OR
|
|
46
|
-
(bi.context::jsonb ? 'inboundMessage')
|
|
47
|
-
)
|
|
48
|
-
)
|
|
49
|
-
SELECT
|
|
50
|
-
day, "EnterpriseId", "ChannelId", "channelName", "messageType",
|
|
51
|
-
COUNT(DISTINCT inbound_id) AS total
|
|
52
|
-
FROM base
|
|
53
|
-
GROUP BY day, "EnterpriseId", "ChannelId", "channelName", "messageType";
|
|
54
|
-
`)
|
|
55
|
-
},
|
|
56
|
-
async down(queryInterface) {
|
|
57
|
-
await queryInterface.sequelize.query(
|
|
58
|
-
`DROP VIEW IF EXISTS channel_message_type_daily;`,
|
|
59
|
-
)
|
|
60
|
-
},
|
|
61
|
-
}
|