@happyvertical/smrt-analytics 0.37.1 → 0.37.3
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/dist/index.js +1576 -1669
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2 -2
- package/dist/playground.js +83 -96
- package/dist/playground.js.map +1 -1
- package/dist/smrt-knowledge.json +10 -10
- package/dist/ui.js +77 -77
- package/dist/ui.js.map +1 -1
- package/package.json +15 -15
package/dist/index.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import { ObjectRegistry,
|
|
1
|
+
import { ObjectRegistry, SmrtCollection, SmrtObject, field, foreignKey, smrt } from "@happyvertical/smrt-core";
|
|
2
2
|
import { definePrompt, resolvePrompt } from "@happyvertical/smrt-prompts";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
|
|
4
|
+
//#region src/__smrt-register__.ts
|
|
5
|
+
ObjectRegistry.registerPackageManifest(new URL("./manifest.json", "" + import.meta.url));
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/prompts.ts
|
|
8
|
+
var smrtAnalyticsAnalyzePerformancePrompt = definePrompt({
|
|
9
|
+
key: "smrtAnalytics.property.analyzePerformance",
|
|
10
|
+
template: `Analyze the analytics performance for this property over the last {period}.
|
|
10
11
|
Consider: traffic trends, user engagement, conversion patterns.
|
|
11
12
|
Property: {propertyDisplayName} ({propertyProvider})`,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
editable: {
|
|
14
|
+
template: true,
|
|
15
|
+
profile: true,
|
|
16
|
+
model: true,
|
|
17
|
+
params: true
|
|
18
|
+
}
|
|
18
19
|
});
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
var smrtAnalyticsAnalyzeResultsPrompt = definePrompt({
|
|
21
|
+
key: "smrtAnalytics.report.analyzeResults",
|
|
22
|
+
template: `Analyze these analytics report results and provide insights:
|
|
22
23
|
|
|
23
24
|
Report: {reportName}
|
|
24
25
|
Dimensions: {reportDimensions}
|
|
@@ -32,16 +33,16 @@ Provide:
|
|
|
32
33
|
1. Key findings
|
|
33
34
|
2. Trends or patterns
|
|
34
35
|
3. Actionable recommendations`,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
editable: {
|
|
37
|
+
template: true,
|
|
38
|
+
profile: true,
|
|
39
|
+
model: true,
|
|
40
|
+
params: true
|
|
41
|
+
}
|
|
41
42
|
});
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
var smrtAnalyticsHasPositiveTrendsPrompt = definePrompt({
|
|
44
|
+
key: "smrtAnalytics.report.hasPositiveTrends",
|
|
45
|
+
template: `Based on these report results, are the metrics showing positive trends?
|
|
45
46
|
|
|
46
47
|
Metrics: {reportMetrics}
|
|
47
48
|
Data: {reportData}
|
|
@@ -50,1108 +51,1050 @@ Consider: user growth, engagement, conversions as positive indicators.
|
|
|
50
51
|
|
|
51
52
|
Begin your response with the single word "yes" or "no" (lower case), then
|
|
52
53
|
optionally provide a one-sentence explanation. Tooling parses the leading
|
|
53
|
-
word
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
word \u2014 "yes" or "true" means positive, anything else means negative.`,
|
|
55
|
+
editable: {
|
|
56
|
+
template: true,
|
|
57
|
+
profile: true,
|
|
58
|
+
model: true,
|
|
59
|
+
params: true
|
|
60
|
+
}
|
|
60
61
|
});
|
|
61
62
|
function promptMessageOptions(ai) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
return {
|
|
64
|
+
...ai.params || {},
|
|
65
|
+
...ai.model ? { model: ai.model } : {},
|
|
66
|
+
...typeof ai.temperature === "number" ? { temperature: ai.temperature } : {},
|
|
67
|
+
...typeof ai.maxTokens === "number" ? { maxTokens: ai.maxTokens } : {}
|
|
68
|
+
};
|
|
68
69
|
}
|
|
70
|
+
//#endregion
|
|
71
|
+
//#region src/types/index.ts
|
|
69
72
|
var AnalyticsProvider = /* @__PURE__ */ ((AnalyticsProvider2) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
AnalyticsProvider2["GA4"] = "ga4";
|
|
74
|
+
AnalyticsProvider2["PLAUSIBLE"] = "plausible";
|
|
75
|
+
AnalyticsProvider2["MATOMO"] = "matomo";
|
|
76
|
+
return AnalyticsProvider2;
|
|
74
77
|
})(AnalyticsProvider || {});
|
|
75
78
|
var AnalyticsPropertyStatus = /* @__PURE__ */ ((AnalyticsPropertyStatus2) => {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
AnalyticsPropertyStatus2["ACTIVE"] = "active";
|
|
80
|
+
AnalyticsPropertyStatus2["INACTIVE"] = "inactive";
|
|
81
|
+
AnalyticsPropertyStatus2["PENDING"] = "pending";
|
|
82
|
+
return AnalyticsPropertyStatus2;
|
|
80
83
|
})(AnalyticsPropertyStatus || {});
|
|
81
84
|
var DataStreamType = /* @__PURE__ */ ((DataStreamType2) => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
DataStreamType2["WEB"] = "WEB_DATA_STREAM";
|
|
86
|
+
DataStreamType2["ANDROID"] = "ANDROID_APP_DATA_STREAM";
|
|
87
|
+
DataStreamType2["IOS"] = "IOS_APP_DATA_STREAM";
|
|
88
|
+
return DataStreamType2;
|
|
86
89
|
})(DataStreamType || {});
|
|
87
90
|
var DataStreamStatus = /* @__PURE__ */ ((DataStreamStatus2) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
DataStreamStatus2["ACTIVE"] = "active";
|
|
92
|
+
DataStreamStatus2["INACTIVE"] = "inactive";
|
|
93
|
+
return DataStreamStatus2;
|
|
91
94
|
})(DataStreamStatus || {});
|
|
92
95
|
var ReportStatus = /* @__PURE__ */ ((ReportStatus2) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
ReportStatus2["DRAFT"] = "draft";
|
|
97
|
+
ReportStatus2["SCHEDULED"] = "scheduled";
|
|
98
|
+
ReportStatus2["RUNNING"] = "running";
|
|
99
|
+
ReportStatus2["COMPLETED"] = "completed";
|
|
100
|
+
ReportStatus2["FAILED"] = "failed";
|
|
101
|
+
return ReportStatus2;
|
|
99
102
|
})(ReportStatus || {});
|
|
100
103
|
var ReportFrequency = /* @__PURE__ */ ((ReportFrequency2) => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
ReportFrequency2["ONCE"] = "once";
|
|
105
|
+
ReportFrequency2["DAILY"] = "daily";
|
|
106
|
+
ReportFrequency2["WEEKLY"] = "weekly";
|
|
107
|
+
ReportFrequency2["MONTHLY"] = "monthly";
|
|
108
|
+
return ReportFrequency2;
|
|
106
109
|
})(ReportFrequency || {});
|
|
107
110
|
var CustomDimensionScope = /* @__PURE__ */ ((CustomDimensionScope2) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
CustomDimensionScope2["EVENT"] = "EVENT";
|
|
112
|
+
CustomDimensionScope2["USER"] = "USER";
|
|
113
|
+
CustomDimensionScope2["ITEM"] = "ITEM";
|
|
114
|
+
return CustomDimensionScope2;
|
|
112
115
|
})(CustomDimensionScope || {});
|
|
113
116
|
var TrackingEventStatus = /* @__PURE__ */ ((TrackingEventStatus2) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
TrackingEventStatus2["PENDING"] = "pending";
|
|
118
|
+
TrackingEventStatus2["SENT"] = "sent";
|
|
119
|
+
TrackingEventStatus2["FAILED"] = "failed";
|
|
120
|
+
return TrackingEventStatus2;
|
|
118
121
|
})(TrackingEventStatus || {});
|
|
119
122
|
var CountingMethod = /* @__PURE__ */ ((CountingMethod2) => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
+
CountingMethod2["ONCE_PER_EVENT"] = "ONCE_PER_EVENT";
|
|
124
|
+
CountingMethod2["ONCE_PER_SESSION"] = "ONCE_PER_SESSION";
|
|
125
|
+
return CountingMethod2;
|
|
123
126
|
})(CountingMethod || {});
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/models/AnalyticsDataStream.ts
|
|
124
129
|
var __defProp$3 = Object.defineProperty;
|
|
125
130
|
var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
|
|
126
131
|
var __decorateClass$3 = (decorators, target, key, kind) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (kind && result) __defProp$3(target, key, result);
|
|
132
|
-
return result;
|
|
132
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
|
|
133
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
134
|
+
if (kind && result) __defProp$3(target, key, result);
|
|
135
|
+
return result;
|
|
133
136
|
};
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
*/
|
|
224
|
-
getPlatformId() {
|
|
225
|
-
if (this.isWeb()) {
|
|
226
|
-
return this.measurementId;
|
|
227
|
-
}
|
|
228
|
-
return this.firebaseAppId;
|
|
229
|
-
}
|
|
137
|
+
var AnalyticsDataStream = class extends SmrtObject {
|
|
138
|
+
tenantId = null;
|
|
139
|
+
propertyId = "";
|
|
140
|
+
/**
|
|
141
|
+
* Human-readable display name
|
|
142
|
+
*/
|
|
143
|
+
displayName = "";
|
|
144
|
+
/**
|
|
145
|
+
* Stream type (WEB, ANDROID, IOS)
|
|
146
|
+
*/
|
|
147
|
+
streamType = DataStreamType.WEB;
|
|
148
|
+
/**
|
|
149
|
+
* External stream ID from provider
|
|
150
|
+
*/
|
|
151
|
+
externalId = "";
|
|
152
|
+
/**
|
|
153
|
+
* Measurement ID for web streams (G-XXXXXXX)
|
|
154
|
+
*/
|
|
155
|
+
measurementId = "";
|
|
156
|
+
/**
|
|
157
|
+
* Firebase App ID for app streams
|
|
158
|
+
*/
|
|
159
|
+
firebaseAppId = "";
|
|
160
|
+
/**
|
|
161
|
+
* Default URI for web streams
|
|
162
|
+
*/
|
|
163
|
+
defaultUri = "";
|
|
164
|
+
/**
|
|
165
|
+
* Bundle ID for iOS apps
|
|
166
|
+
*/
|
|
167
|
+
bundleId = "";
|
|
168
|
+
/**
|
|
169
|
+
* Package name for Android apps
|
|
170
|
+
*/
|
|
171
|
+
packageName = "";
|
|
172
|
+
/**
|
|
173
|
+
* Stream status
|
|
174
|
+
*/
|
|
175
|
+
status = DataStreamStatus.ACTIVE;
|
|
176
|
+
/**
|
|
177
|
+
* Enhanced measurement enabled
|
|
178
|
+
*/
|
|
179
|
+
enhancedMeasurement = true;
|
|
180
|
+
constructor(options = {}) {
|
|
181
|
+
super(options);
|
|
182
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
183
|
+
if (options.propertyId !== void 0) this.propertyId = options.propertyId;
|
|
184
|
+
if (options.displayName !== void 0) this.displayName = options.displayName;
|
|
185
|
+
if (options.streamType !== void 0) this.streamType = options.streamType;
|
|
186
|
+
if (options.externalId !== void 0) this.externalId = options.externalId;
|
|
187
|
+
if (options.measurementId !== void 0) this.measurementId = options.measurementId;
|
|
188
|
+
if (options.firebaseAppId !== void 0) this.firebaseAppId = options.firebaseAppId;
|
|
189
|
+
if (options.defaultUri !== void 0) this.defaultUri = options.defaultUri;
|
|
190
|
+
if (options.bundleId !== void 0) this.bundleId = options.bundleId;
|
|
191
|
+
if (options.packageName !== void 0) this.packageName = options.packageName;
|
|
192
|
+
if (options.status !== void 0) this.status = options.status;
|
|
193
|
+
if (options.enhancedMeasurement !== void 0) this.enhancedMeasurement = options.enhancedMeasurement;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Check if this is a web stream
|
|
197
|
+
*/
|
|
198
|
+
isWeb() {
|
|
199
|
+
return this.streamType === DataStreamType.WEB;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check if this is an iOS app stream
|
|
203
|
+
*/
|
|
204
|
+
isIOS() {
|
|
205
|
+
return this.streamType === DataStreamType.IOS;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Check if this is an Android app stream
|
|
209
|
+
*/
|
|
210
|
+
isAndroid() {
|
|
211
|
+
return this.streamType === DataStreamType.ANDROID;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Check if this is a mobile app stream (iOS or Android)
|
|
215
|
+
*/
|
|
216
|
+
isMobileApp() {
|
|
217
|
+
return this.isIOS() || this.isAndroid();
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get the platform identifier (measurementId for web, firebaseAppId for apps)
|
|
221
|
+
*/
|
|
222
|
+
getPlatformId() {
|
|
223
|
+
if (this.isWeb()) return this.measurementId;
|
|
224
|
+
return this.firebaseAppId;
|
|
225
|
+
}
|
|
230
226
|
};
|
|
231
|
-
__decorateClass$3([
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
227
|
+
__decorateClass$3([tenantId({ nullable: true })], AnalyticsDataStream.prototype, "tenantId", 2);
|
|
228
|
+
__decorateClass$3([foreignKey("AnalyticsProperty")], AnalyticsDataStream.prototype, "propertyId", 2);
|
|
229
|
+
AnalyticsDataStream = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
|
|
230
|
+
tableStrategy: "sti",
|
|
231
|
+
api: { include: [
|
|
232
|
+
"list",
|
|
233
|
+
"get",
|
|
234
|
+
"create",
|
|
235
|
+
"update"
|
|
236
|
+
] },
|
|
237
|
+
mcp: { include: ["list", "get"] },
|
|
238
|
+
cli: true
|
|
239
|
+
})], AnalyticsDataStream);
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region src/collections/AnalyticsDataStreamCollection.ts
|
|
242
|
+
var AnalyticsDataStreamCollection = class extends SmrtCollection {
|
|
243
|
+
static _itemClass = AnalyticsDataStream;
|
|
244
|
+
/**
|
|
245
|
+
* Find streams by property
|
|
246
|
+
*
|
|
247
|
+
* @param propertyId - Parent property ID
|
|
248
|
+
* @returns Array of data streams
|
|
249
|
+
*/
|
|
250
|
+
async findByProperty(propertyId) {
|
|
251
|
+
return await this.list({
|
|
252
|
+
where: { propertyId },
|
|
253
|
+
orderBy: "displayName ASC"
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Find stream by external ID
|
|
258
|
+
*
|
|
259
|
+
* @param externalId - External stream ID from provider
|
|
260
|
+
* @returns Matching stream or null
|
|
261
|
+
*/
|
|
262
|
+
async findByExternalId(externalId) {
|
|
263
|
+
const results = await this.list({
|
|
264
|
+
where: { externalId },
|
|
265
|
+
limit: 1
|
|
266
|
+
});
|
|
267
|
+
return results.length > 0 ? results[0] : null;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Find stream by measurement ID (web streams)
|
|
271
|
+
*
|
|
272
|
+
* @param measurementId - GA4 measurement ID (G-XXXXXXX)
|
|
273
|
+
* @returns Matching stream or null
|
|
274
|
+
*/
|
|
275
|
+
async findByMeasurementId(measurementId) {
|
|
276
|
+
const results = await this.list({
|
|
277
|
+
where: { measurementId },
|
|
278
|
+
limit: 1
|
|
279
|
+
});
|
|
280
|
+
return results.length > 0 ? results[0] : null;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Find streams by type
|
|
284
|
+
*
|
|
285
|
+
* @param streamType - Stream type (WEB, ANDROID, IOS)
|
|
286
|
+
* @returns Array of matching streams
|
|
287
|
+
*/
|
|
288
|
+
async findByType(streamType) {
|
|
289
|
+
return await this.list({
|
|
290
|
+
where: { streamType },
|
|
291
|
+
orderBy: "displayName ASC"
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Find all web streams
|
|
296
|
+
*/
|
|
297
|
+
async findWebStreams() {
|
|
298
|
+
return await this.findByType(DataStreamType.WEB);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Find all iOS app streams
|
|
302
|
+
*/
|
|
303
|
+
async findIOSStreams() {
|
|
304
|
+
return await this.findByType(DataStreamType.IOS);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Find all Android app streams
|
|
308
|
+
*/
|
|
309
|
+
async findAndroidStreams() {
|
|
310
|
+
return await this.findByType(DataStreamType.ANDROID);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Find all mobile app streams (iOS + Android)
|
|
314
|
+
*/
|
|
315
|
+
async findMobileStreams() {
|
|
316
|
+
const ios = await this.findIOSStreams();
|
|
317
|
+
const android = await this.findAndroidStreams();
|
|
318
|
+
return [...ios, ...android].sort((a, b) => a.displayName.localeCompare(b.displayName));
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Find streams by status
|
|
322
|
+
*
|
|
323
|
+
* @param status - Stream status
|
|
324
|
+
* @returns Array of matching streams
|
|
325
|
+
*/
|
|
326
|
+
async findByStatus(status) {
|
|
327
|
+
return await this.list({
|
|
328
|
+
where: { status },
|
|
329
|
+
orderBy: "displayName ASC"
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Find all active streams
|
|
334
|
+
*/
|
|
335
|
+
async findActive() {
|
|
336
|
+
return await this.findByStatus(DataStreamStatus.ACTIVE);
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Find active streams for a property
|
|
340
|
+
*
|
|
341
|
+
* @param propertyId - Parent property ID
|
|
342
|
+
* @returns Array of active streams
|
|
343
|
+
*/
|
|
344
|
+
async findActiveByProperty(propertyId) {
|
|
345
|
+
return await this.list({
|
|
346
|
+
where: {
|
|
347
|
+
propertyId,
|
|
348
|
+
status: DataStreamStatus.ACTIVE
|
|
349
|
+
},
|
|
350
|
+
orderBy: "displayName ASC"
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/models/AnalyticsEvent.ts
|
|
360
356
|
var __defProp$2 = Object.defineProperty;
|
|
361
357
|
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
|
|
362
358
|
var __decorateClass$2 = (decorators, target, key, kind) => {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (kind && result) __defProp$2(target, key, result);
|
|
368
|
-
return result;
|
|
359
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
|
|
360
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
361
|
+
if (kind && result) __defProp$2(target, key, result);
|
|
362
|
+
return result;
|
|
369
363
|
};
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
timestamp: this.eventTimestamp.getTime() * 1e3,
|
|
537
|
-
// Convert to microseconds
|
|
538
|
-
nonPersonalizedAds: this.nonPersonalizedAds
|
|
539
|
-
};
|
|
540
|
-
}
|
|
364
|
+
var AnalyticsEvent = class extends SmrtObject {
|
|
365
|
+
tenantId = null;
|
|
366
|
+
propertyId = "";
|
|
367
|
+
/**
|
|
368
|
+
* Event name (e.g., 'purchase', 'page_view', 'sign_up')
|
|
369
|
+
*/
|
|
370
|
+
eventName = "";
|
|
371
|
+
/**
|
|
372
|
+
* Client ID for anonymous tracking
|
|
373
|
+
*/
|
|
374
|
+
clientId = "";
|
|
375
|
+
/**
|
|
376
|
+
* User ID for identified tracking
|
|
377
|
+
*/
|
|
378
|
+
userId = "";
|
|
379
|
+
/**
|
|
380
|
+
* Event parameters (JSON)
|
|
381
|
+
*/
|
|
382
|
+
params = "{}";
|
|
383
|
+
/**
|
|
384
|
+
* Event timestamp
|
|
385
|
+
*/
|
|
386
|
+
eventTimestamp = /* @__PURE__ */ new Date();
|
|
387
|
+
/**
|
|
388
|
+
* Tracking status
|
|
389
|
+
*/
|
|
390
|
+
status = TrackingEventStatus.PENDING;
|
|
391
|
+
/**
|
|
392
|
+
* Timestamp when sent to provider
|
|
393
|
+
*/
|
|
394
|
+
sentAt = null;
|
|
395
|
+
/**
|
|
396
|
+
* Error message if sending failed
|
|
397
|
+
*/
|
|
398
|
+
errorMessage = "";
|
|
399
|
+
/**
|
|
400
|
+
* Retry count
|
|
401
|
+
*/
|
|
402
|
+
retryCount = 0;
|
|
403
|
+
/**
|
|
404
|
+
* Disable personalized ads
|
|
405
|
+
*/
|
|
406
|
+
nonPersonalizedAds = false;
|
|
407
|
+
/**
|
|
408
|
+
* Session ID
|
|
409
|
+
*/
|
|
410
|
+
sessionId = "";
|
|
411
|
+
/**
|
|
412
|
+
* Page path (for pageview events)
|
|
413
|
+
*/
|
|
414
|
+
pagePath = "";
|
|
415
|
+
/**
|
|
416
|
+
* Page title (for pageview events)
|
|
417
|
+
*/
|
|
418
|
+
pageTitle = "";
|
|
419
|
+
/**
|
|
420
|
+
* User agent
|
|
421
|
+
*/
|
|
422
|
+
userAgent = "";
|
|
423
|
+
/**
|
|
424
|
+
* IP address (anonymized)
|
|
425
|
+
*/
|
|
426
|
+
ipAddress = "";
|
|
427
|
+
constructor(options = {}) {
|
|
428
|
+
super(options);
|
|
429
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
430
|
+
if (options.propertyId !== void 0) this.propertyId = options.propertyId;
|
|
431
|
+
if (options.eventName !== void 0) this.eventName = options.eventName;
|
|
432
|
+
if (options.clientId !== void 0) this.clientId = options.clientId;
|
|
433
|
+
if (options.userId !== void 0) this.userId = options.userId;
|
|
434
|
+
if (options.params !== void 0) this.params = options.params;
|
|
435
|
+
if (options.eventTimestamp !== void 0) this.eventTimestamp = options.eventTimestamp;
|
|
436
|
+
if (options.status !== void 0) this.status = options.status;
|
|
437
|
+
if (options.sentAt !== void 0) this.sentAt = options.sentAt;
|
|
438
|
+
if (options.errorMessage !== void 0) this.errorMessage = options.errorMessage;
|
|
439
|
+
if (options.retryCount !== void 0) this.retryCount = options.retryCount;
|
|
440
|
+
if (options.nonPersonalizedAds !== void 0) this.nonPersonalizedAds = options.nonPersonalizedAds;
|
|
441
|
+
if (options.sessionId !== void 0) this.sessionId = options.sessionId;
|
|
442
|
+
if (options.pagePath !== void 0) this.pagePath = options.pagePath;
|
|
443
|
+
if (options.pageTitle !== void 0) this.pageTitle = options.pageTitle;
|
|
444
|
+
if (options.userAgent !== void 0) this.userAgent = options.userAgent;
|
|
445
|
+
if (options.ipAddress !== void 0) this.ipAddress = options.ipAddress;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Get parsed event parameters
|
|
449
|
+
*/
|
|
450
|
+
getParams() {
|
|
451
|
+
try {
|
|
452
|
+
return JSON.parse(this.params);
|
|
453
|
+
} catch {
|
|
454
|
+
return {};
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Set event parameters
|
|
459
|
+
*/
|
|
460
|
+
setParams(params) {
|
|
461
|
+
this.params = JSON.stringify(params);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Add a parameter to the event
|
|
465
|
+
*/
|
|
466
|
+
addParam(key, value) {
|
|
467
|
+
const params = this.getParams();
|
|
468
|
+
params[key] = value;
|
|
469
|
+
this.setParams(params);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Check if this is a pageview event
|
|
473
|
+
*/
|
|
474
|
+
isPageview() {
|
|
475
|
+
return this.eventName === "page_view" || this.eventName === "pageview";
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Check if this is a conversion event
|
|
479
|
+
*/
|
|
480
|
+
isConversion() {
|
|
481
|
+
return [
|
|
482
|
+
"purchase",
|
|
483
|
+
"sign_up",
|
|
484
|
+
"generate_lead",
|
|
485
|
+
"begin_checkout"
|
|
486
|
+
].includes(this.eventName);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Mark as sent successfully
|
|
490
|
+
*/
|
|
491
|
+
markSent() {
|
|
492
|
+
this.status = TrackingEventStatus.SENT;
|
|
493
|
+
this.sentAt = /* @__PURE__ */ new Date();
|
|
494
|
+
this.errorMessage = "";
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Mark as failed
|
|
498
|
+
*/
|
|
499
|
+
markFailed(error) {
|
|
500
|
+
this.status = TrackingEventStatus.FAILED;
|
|
501
|
+
this.errorMessage = error;
|
|
502
|
+
this.retryCount++;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Reset for retry
|
|
506
|
+
*/
|
|
507
|
+
resetForRetry() {
|
|
508
|
+
this.status = TrackingEventStatus.PENDING;
|
|
509
|
+
this.sentAt = null;
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Check if event should be retried
|
|
513
|
+
*/
|
|
514
|
+
shouldRetry(maxRetries = 3) {
|
|
515
|
+
return this.status === TrackingEventStatus.FAILED && this.retryCount < maxRetries;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Build SDK TrackEvent object
|
|
519
|
+
*/
|
|
520
|
+
toTrackEvent() {
|
|
521
|
+
return {
|
|
522
|
+
name: this.eventName,
|
|
523
|
+
params: this.getParams(),
|
|
524
|
+
clientId: this.clientId || void 0,
|
|
525
|
+
userId: this.userId || void 0,
|
|
526
|
+
timestamp: this.eventTimestamp.getTime() * 1e3,
|
|
527
|
+
nonPersonalizedAds: this.nonPersonalizedAds
|
|
528
|
+
};
|
|
529
|
+
}
|
|
541
530
|
};
|
|
542
|
-
__decorateClass$2([
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
const list = map.get(e.propertyId);
|
|
957
|
-
if (list) list.push(e);
|
|
958
|
-
else map.set(e.propertyId, [e]);
|
|
959
|
-
}
|
|
960
|
-
for (const propertyId of propertyIds) {
|
|
961
|
-
const todayPageviewEvents = todayByProperty.get(propertyId) ?? [];
|
|
962
|
-
const yesterdayPageviewEvents = yesterdayByProperty.get(propertyId) ?? [];
|
|
963
|
-
const todayClients = new Set(todayPageviewEvents.map((e) => e.clientId));
|
|
964
|
-
const yesterdayClients = new Set(
|
|
965
|
-
yesterdayPageviewEvents.map((e) => e.clientId)
|
|
966
|
-
);
|
|
967
|
-
const todayPageviews = todayPageviewEvents.length;
|
|
968
|
-
const yesterdayPageviews = yesterdayPageviewEvents.length;
|
|
969
|
-
const { trend, trendPercent } = this.classifyTrend(
|
|
970
|
-
todayPageviews,
|
|
971
|
-
yesterdayPageviews
|
|
972
|
-
);
|
|
973
|
-
results.set(propertyId, {
|
|
974
|
-
todayPageviews,
|
|
975
|
-
todayUsers: todayClients.size,
|
|
976
|
-
yesterdayPageviews,
|
|
977
|
-
yesterdayUsers: yesterdayClients.size,
|
|
978
|
-
trend,
|
|
979
|
-
trendPercent
|
|
980
|
-
});
|
|
981
|
-
}
|
|
982
|
-
return results;
|
|
983
|
-
}
|
|
984
|
-
}
|
|
531
|
+
__decorateClass$2([tenantId({ nullable: true })], AnalyticsEvent.prototype, "tenantId", 2);
|
|
532
|
+
__decorateClass$2([foreignKey("AnalyticsProperty")], AnalyticsEvent.prototype, "propertyId", 2);
|
|
533
|
+
AnalyticsEvent = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
|
|
534
|
+
tableStrategy: "sti",
|
|
535
|
+
api: { include: [
|
|
536
|
+
"list",
|
|
537
|
+
"get",
|
|
538
|
+
"create"
|
|
539
|
+
] },
|
|
540
|
+
mcp: { include: [
|
|
541
|
+
"list",
|
|
542
|
+
"get",
|
|
543
|
+
"track"
|
|
544
|
+
] },
|
|
545
|
+
cli: true
|
|
546
|
+
})], AnalyticsEvent);
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/collections/AnalyticsEventCollection.ts
|
|
549
|
+
var AnalyticsEventCollection = class extends SmrtCollection {
|
|
550
|
+
static _itemClass = AnalyticsEvent;
|
|
551
|
+
/**
|
|
552
|
+
* Offset of `timeZone` at `instant`, in milliseconds (wall-clock minus UTC).
|
|
553
|
+
*
|
|
554
|
+
* Reads the zone's wall-clock Y/M/D h:m:s for `instant` via
|
|
555
|
+
* `Intl.DateTimeFormat` parts and subtracts the real UTC instant. Positive
|
|
556
|
+
* east of UTC, negative west (e.g. `America/Los_Angeles` returns roughly
|
|
557
|
+
* `-7h`/`-8h` depending on DST).
|
|
558
|
+
*
|
|
559
|
+
* @throws RangeError if `timeZone` is not a valid IANA identifier.
|
|
560
|
+
*/
|
|
561
|
+
zoneOffsetMs(instant, timeZone) {
|
|
562
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
563
|
+
timeZone,
|
|
564
|
+
year: "numeric",
|
|
565
|
+
month: "2-digit",
|
|
566
|
+
day: "2-digit",
|
|
567
|
+
hour: "2-digit",
|
|
568
|
+
minute: "2-digit",
|
|
569
|
+
second: "2-digit",
|
|
570
|
+
hour12: false
|
|
571
|
+
}).formatToParts(instant);
|
|
572
|
+
const lookup = (type) => Number.parseInt(parts.find((p) => p.type === type)?.value ?? "0", 10);
|
|
573
|
+
let hour = lookup("hour");
|
|
574
|
+
if (hour === 24) hour = 0;
|
|
575
|
+
return Date.UTC(lookup("year"), lookup("month") - 1, lookup("day"), hour, lookup("minute"), lookup("second")) - instant.getTime();
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Resolve the UTC instant marking the start of the calendar day (00:00) that
|
|
579
|
+
* `instant` falls on **within the given IANA time zone**.
|
|
580
|
+
*
|
|
581
|
+
* Day-over-day buckets ("today vs yesterday") must respect the property's
|
|
582
|
+
* configured `timeZone` (defaults to `America/Los_Angeles`), otherwise a
|
|
583
|
+
* pageview at 11:30pm local time — already the next UTC day — is bucketed
|
|
584
|
+
* into the wrong day. We read the wall-clock civil date for the zone, then
|
|
585
|
+
* map that date's local midnight back to a UTC instant, correcting for the
|
|
586
|
+
* zone offset (and re-correcting once across a DST boundary).
|
|
587
|
+
*
|
|
588
|
+
* Invalid/unknown zone identifiers fall back to UTC day boundaries (matching
|
|
589
|
+
* the previous behaviour) rather than throwing.
|
|
590
|
+
*
|
|
591
|
+
* @param instant - Reference instant.
|
|
592
|
+
* @param timeZone - IANA time zone (e.g. `America/Los_Angeles`).
|
|
593
|
+
* @returns UTC `Date` for local midnight of the day `instant` is in.
|
|
594
|
+
*/
|
|
595
|
+
startOfDayInZone(instant, timeZone) {
|
|
596
|
+
let civil;
|
|
597
|
+
try {
|
|
598
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
599
|
+
timeZone,
|
|
600
|
+
year: "numeric",
|
|
601
|
+
month: "2-digit",
|
|
602
|
+
day: "2-digit"
|
|
603
|
+
}).formatToParts(instant);
|
|
604
|
+
const lookup = (type) => Number.parseInt(parts.find((p) => p.type === type)?.value ?? "0", 10);
|
|
605
|
+
civil = {
|
|
606
|
+
year: lookup("year"),
|
|
607
|
+
month: lookup("month"),
|
|
608
|
+
day: lookup("day")
|
|
609
|
+
};
|
|
610
|
+
} catch {
|
|
611
|
+
return new Date(Date.UTC(instant.getUTCFullYear(), instant.getUTCMonth(), instant.getUTCDate()));
|
|
612
|
+
}
|
|
613
|
+
const guess = Date.UTC(civil.year, civil.month - 1, civil.day);
|
|
614
|
+
const offset = this.zoneOffsetMs(new Date(guess), timeZone);
|
|
615
|
+
let utc = guess - offset;
|
|
616
|
+
const offset2 = this.zoneOffsetMs(new Date(utc), timeZone);
|
|
617
|
+
if (offset2 !== offset) utc = guess - offset2;
|
|
618
|
+
return new Date(utc);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Resolve the UTC instant for the start of the day *before* `todayStart`'s
|
|
622
|
+
* local day, in `timeZone`.
|
|
623
|
+
*
|
|
624
|
+
* Steps back 12h from local midnight (landing safely inside the previous
|
|
625
|
+
* civil day regardless of DST — a naive `- 24h` skips a day across
|
|
626
|
+
* spring-forward), then re-resolves start-of-day.
|
|
627
|
+
*
|
|
628
|
+
* @param todayStart - Local-midnight UTC instant from {@link startOfDayInZone}.
|
|
629
|
+
* @param timeZone - IANA time zone.
|
|
630
|
+
* @returns UTC `Date` for local midnight of the prior calendar day.
|
|
631
|
+
*/
|
|
632
|
+
startOfYesterdayInZone(todayStart, timeZone) {
|
|
633
|
+
return this.startOfDayInZone(/* @__PURE__ */ new Date(todayStart.getTime() - 12 * 36e5), timeZone);
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Classify a day-over-day change into a trend direction + percent.
|
|
637
|
+
*
|
|
638
|
+
* - `yesterday > 0`: percent = rounded delta; >5% up, <-5% down, else flat.
|
|
639
|
+
* - `yesterday === 0 && today > 0`: a brand-new surge from a zero baseline —
|
|
640
|
+
* classified `up` with a `null` percent (no finite percentage exists), so
|
|
641
|
+
* the UI renders "new" rather than a misleading flat 0%.
|
|
642
|
+
* - `yesterday === 0 && today === 0`: flat, 0%.
|
|
643
|
+
*
|
|
644
|
+
* @param today - Today's count.
|
|
645
|
+
* @param yesterday - Yesterday's count.
|
|
646
|
+
* @returns Trend direction and percent (null when growing from zero).
|
|
647
|
+
*/
|
|
648
|
+
classifyTrend(today, yesterday) {
|
|
649
|
+
if (yesterday > 0) {
|
|
650
|
+
const change = (today - yesterday) / yesterday * 100;
|
|
651
|
+
const trendPercent = Math.round(change);
|
|
652
|
+
let trend = "flat";
|
|
653
|
+
if (change > 5) trend = "up";
|
|
654
|
+
else if (change < -5) trend = "down";
|
|
655
|
+
return {
|
|
656
|
+
trend,
|
|
657
|
+
trendPercent
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
if (today > 0) return {
|
|
661
|
+
trend: "up",
|
|
662
|
+
trendPercent: null
|
|
663
|
+
};
|
|
664
|
+
return {
|
|
665
|
+
trend: "flat",
|
|
666
|
+
trendPercent: 0
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Find events by property
|
|
671
|
+
*
|
|
672
|
+
* @param propertyId - Parent property ID
|
|
673
|
+
* @returns Array of events
|
|
674
|
+
*/
|
|
675
|
+
async findByProperty(propertyId) {
|
|
676
|
+
return await this.list({
|
|
677
|
+
where: { propertyId },
|
|
678
|
+
orderBy: "eventTimestamp DESC"
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Find events by event name
|
|
683
|
+
*
|
|
684
|
+
* @param eventName - Event name to filter by
|
|
685
|
+
* @returns Array of matching events
|
|
686
|
+
*/
|
|
687
|
+
async findByEventName(eventName) {
|
|
688
|
+
return await this.list({
|
|
689
|
+
where: { eventName },
|
|
690
|
+
orderBy: "eventTimestamp DESC"
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Find events by client ID
|
|
695
|
+
*
|
|
696
|
+
* @param clientId - Client ID
|
|
697
|
+
* @returns Array of events for this client
|
|
698
|
+
*/
|
|
699
|
+
async findByClientId(clientId) {
|
|
700
|
+
return await this.list({
|
|
701
|
+
where: { clientId },
|
|
702
|
+
orderBy: "eventTimestamp DESC"
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Find events by user ID
|
|
707
|
+
*
|
|
708
|
+
* @param userId - User ID
|
|
709
|
+
* @returns Array of events for this user
|
|
710
|
+
*/
|
|
711
|
+
async findByUserId(userId) {
|
|
712
|
+
return await this.list({
|
|
713
|
+
where: { userId },
|
|
714
|
+
orderBy: "eventTimestamp DESC"
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Find events by status
|
|
719
|
+
*
|
|
720
|
+
* @param status - Tracking event status
|
|
721
|
+
* @returns Array of matching events
|
|
722
|
+
*/
|
|
723
|
+
async findByStatus(status) {
|
|
724
|
+
return await this.list({
|
|
725
|
+
where: { status },
|
|
726
|
+
orderBy: "eventTimestamp DESC"
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Find all pending events
|
|
731
|
+
*/
|
|
732
|
+
async findPending() {
|
|
733
|
+
return await this.findByStatus(TrackingEventStatus.PENDING);
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Find all sent events
|
|
737
|
+
*/
|
|
738
|
+
async findSent() {
|
|
739
|
+
return await this.findByStatus(TrackingEventStatus.SENT);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Find all failed events
|
|
743
|
+
*/
|
|
744
|
+
async findFailed() {
|
|
745
|
+
return await this.findByStatus(TrackingEventStatus.FAILED);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Find events that should be retried
|
|
749
|
+
*
|
|
750
|
+
* @param maxRetries - Maximum retry count
|
|
751
|
+
* @returns Array of events eligible for retry
|
|
752
|
+
*/
|
|
753
|
+
async findForRetry(maxRetries = 3) {
|
|
754
|
+
return (await this.findFailed()).filter((e) => e.shouldRetry(maxRetries));
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Find pending events for a property
|
|
758
|
+
*
|
|
759
|
+
* @param propertyId - Parent property ID
|
|
760
|
+
* @returns Array of pending events
|
|
761
|
+
*/
|
|
762
|
+
async findPendingByProperty(propertyId) {
|
|
763
|
+
return await this.list({
|
|
764
|
+
where: {
|
|
765
|
+
propertyId,
|
|
766
|
+
status: TrackingEventStatus.PENDING
|
|
767
|
+
},
|
|
768
|
+
orderBy: "eventTimestamp ASC"
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Find events by date range
|
|
773
|
+
*
|
|
774
|
+
* @param startDate - Start date
|
|
775
|
+
* @param endDate - End date
|
|
776
|
+
* @returns Array of events in date range
|
|
777
|
+
*/
|
|
778
|
+
async findByDateRange(startDate, endDate) {
|
|
779
|
+
return await this.list({
|
|
780
|
+
where: {
|
|
781
|
+
"eventTimestamp >=": startDate.toISOString(),
|
|
782
|
+
"eventTimestamp <=": endDate.toISOString()
|
|
783
|
+
},
|
|
784
|
+
orderBy: "eventTimestamp DESC"
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Find conversion events
|
|
789
|
+
*
|
|
790
|
+
* @param propertyId - Optional property ID filter
|
|
791
|
+
* @returns Array of conversion events
|
|
792
|
+
*/
|
|
793
|
+
async findConversions(propertyId) {
|
|
794
|
+
const conversionEvents = [
|
|
795
|
+
"purchase",
|
|
796
|
+
"sign_up",
|
|
797
|
+
"generate_lead",
|
|
798
|
+
"begin_checkout"
|
|
799
|
+
];
|
|
800
|
+
return (propertyId ? await this.findByProperty(propertyId) : await this.list({ orderBy: "eventTimestamp DESC" })).filter((e) => conversionEvents.includes(e.eventName));
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Find pageview events
|
|
804
|
+
*
|
|
805
|
+
* @param propertyId - Optional property ID filter
|
|
806
|
+
* @returns Array of pageview events
|
|
807
|
+
*/
|
|
808
|
+
async findPageviews(propertyId) {
|
|
809
|
+
const where = { eventName: "page_view" };
|
|
810
|
+
if (propertyId) where.propertyId = propertyId;
|
|
811
|
+
return await this.list({
|
|
812
|
+
where,
|
|
813
|
+
orderBy: "eventTimestamp DESC"
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
/**
|
|
817
|
+
* Count events by event name for a property
|
|
818
|
+
*
|
|
819
|
+
* @param propertyId - Property ID
|
|
820
|
+
* @returns Map of event name to count
|
|
821
|
+
*/
|
|
822
|
+
async countByEventName(propertyId) {
|
|
823
|
+
const events = await this.findByProperty(propertyId);
|
|
824
|
+
const counts = /* @__PURE__ */ new Map();
|
|
825
|
+
for (const event of events) {
|
|
826
|
+
const current = counts.get(event.eventName) || 0;
|
|
827
|
+
counts.set(event.eventName, current + 1);
|
|
828
|
+
}
|
|
829
|
+
return counts;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Get event stats for a property
|
|
833
|
+
*
|
|
834
|
+
* @param propertyId - Property ID
|
|
835
|
+
* @returns Event statistics
|
|
836
|
+
*/
|
|
837
|
+
async getPropertyStats(propertyId) {
|
|
838
|
+
const events = await this.findByProperty(propertyId);
|
|
839
|
+
return {
|
|
840
|
+
total: events.length,
|
|
841
|
+
pending: events.filter((e) => e.status === TrackingEventStatus.PENDING).length,
|
|
842
|
+
sent: events.filter((e) => e.status === TrackingEventStatus.SENT).length,
|
|
843
|
+
failed: events.filter((e) => e.status === TrackingEventStatus.FAILED).length,
|
|
844
|
+
conversions: events.filter((e) => e.isConversion()).length,
|
|
845
|
+
pageviews: events.filter((e) => e.isPageview()).length
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Get day-over-day pageview stats with trend for a property.
|
|
850
|
+
*
|
|
851
|
+
* Compares today's pageview count against yesterday's to produce a
|
|
852
|
+
* trend direction and percentage change. A threshold of 5% is used
|
|
853
|
+
* to classify 'up' vs 'down' vs 'flat'; growth from a zero baseline is
|
|
854
|
+
* classified `up` with a `null` percent (see {@link classifyTrend}).
|
|
855
|
+
*
|
|
856
|
+
* Day boundaries are computed in `timeZone` (an IANA identifier such as the
|
|
857
|
+
* property's `AnalyticsProperty.timeZone`, which defaults to
|
|
858
|
+
* `America/Los_Angeles`) so an event near local midnight buckets into the
|
|
859
|
+
* correct calendar day. Defaults to `'UTC'` when omitted.
|
|
860
|
+
*
|
|
861
|
+
* @param propertyId - Property ID
|
|
862
|
+
* @param now - Optional current date (for testing)
|
|
863
|
+
* @param timeZone - IANA time zone for day boundaries (default `'UTC'`)
|
|
864
|
+
* @returns Stats with trend
|
|
865
|
+
*/
|
|
866
|
+
async getPropertyStatsWithTrend(propertyId, now, timeZone = "UTC") {
|
|
867
|
+
const currentTime = now || /* @__PURE__ */ new Date();
|
|
868
|
+
const todayStart = this.startOfDayInZone(currentTime, timeZone);
|
|
869
|
+
const yesterdayStart = this.startOfYesterdayInZone(todayStart, timeZone);
|
|
870
|
+
const allPageviewEvents = await this.list({ where: {
|
|
871
|
+
propertyId,
|
|
872
|
+
eventName: "page_view",
|
|
873
|
+
"eventTimestamp >=": yesterdayStart.toISOString(),
|
|
874
|
+
"eventTimestamp <=": currentTime.toISOString()
|
|
875
|
+
} });
|
|
876
|
+
const todayPageviewEvents = allPageviewEvents.filter((e) => new Date(e.eventTimestamp) >= todayStart);
|
|
877
|
+
const yesterdayPageviewEvents = allPageviewEvents.filter((e) => new Date(e.eventTimestamp) < todayStart);
|
|
878
|
+
const todayClients = new Set(todayPageviewEvents.map((e) => e.clientId));
|
|
879
|
+
const yesterdayClients = new Set(yesterdayPageviewEvents.map((e) => e.clientId));
|
|
880
|
+
const todayPageviews = todayPageviewEvents.length;
|
|
881
|
+
const yesterdayPageviews = yesterdayPageviewEvents.length;
|
|
882
|
+
const { trend, trendPercent } = this.classifyTrend(todayPageviews, yesterdayPageviews);
|
|
883
|
+
return {
|
|
884
|
+
todayPageviews,
|
|
885
|
+
todayUsers: todayClients.size,
|
|
886
|
+
yesterdayPageviews,
|
|
887
|
+
yesterdayUsers: yesterdayClients.size,
|
|
888
|
+
trend,
|
|
889
|
+
trendPercent
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Get day-over-day stats for multiple properties in batch.
|
|
894
|
+
*
|
|
895
|
+
* Day boundaries are computed in `timeZone` (default `'UTC'`); see
|
|
896
|
+
* {@link getPropertyStatsWithTrend}. A single zone applies to the whole
|
|
897
|
+
* batch, so callers mixing properties with different `timeZone` values
|
|
898
|
+
* should batch per zone (or fall back to per-property calls).
|
|
899
|
+
*
|
|
900
|
+
* @param propertyIds - Array of property IDs
|
|
901
|
+
* @param now - Optional current date (for testing)
|
|
902
|
+
* @param timeZone - IANA time zone for day boundaries (default `'UTC'`)
|
|
903
|
+
* @returns Map of propertyId to stats
|
|
904
|
+
*/
|
|
905
|
+
async getBatchPropertyStats(propertyIds, now, timeZone = "UTC") {
|
|
906
|
+
const results = /* @__PURE__ */ new Map();
|
|
907
|
+
const currentTime = now || /* @__PURE__ */ new Date();
|
|
908
|
+
const todayStart = this.startOfDayInZone(currentTime, timeZone);
|
|
909
|
+
const yesterdayStart = this.startOfYesterdayInZone(todayStart, timeZone);
|
|
910
|
+
const allEvents = await this.list({ where: {
|
|
911
|
+
eventName: "page_view",
|
|
912
|
+
"eventTimestamp >=": yesterdayStart.toISOString(),
|
|
913
|
+
"eventTimestamp <=": currentTime.toISOString()
|
|
914
|
+
} });
|
|
915
|
+
const todayByProperty = /* @__PURE__ */ new Map();
|
|
916
|
+
const yesterdayByProperty = /* @__PURE__ */ new Map();
|
|
917
|
+
for (const e of allEvents) {
|
|
918
|
+
const map = new Date(e.eventTimestamp) >= todayStart ? todayByProperty : yesterdayByProperty;
|
|
919
|
+
const list = map.get(e.propertyId);
|
|
920
|
+
if (list) list.push(e);
|
|
921
|
+
else map.set(e.propertyId, [e]);
|
|
922
|
+
}
|
|
923
|
+
for (const propertyId of propertyIds) {
|
|
924
|
+
const todayPageviewEvents = todayByProperty.get(propertyId) ?? [];
|
|
925
|
+
const yesterdayPageviewEvents = yesterdayByProperty.get(propertyId) ?? [];
|
|
926
|
+
const todayClients = new Set(todayPageviewEvents.map((e) => e.clientId));
|
|
927
|
+
const yesterdayClients = new Set(yesterdayPageviewEvents.map((e) => e.clientId));
|
|
928
|
+
const todayPageviews = todayPageviewEvents.length;
|
|
929
|
+
const yesterdayPageviews = yesterdayPageviewEvents.length;
|
|
930
|
+
const { trend, trendPercent } = this.classifyTrend(todayPageviews, yesterdayPageviews);
|
|
931
|
+
results.set(propertyId, {
|
|
932
|
+
todayPageviews,
|
|
933
|
+
todayUsers: todayClients.size,
|
|
934
|
+
yesterdayPageviews,
|
|
935
|
+
yesterdayUsers: yesterdayClients.size,
|
|
936
|
+
trend,
|
|
937
|
+
trendPercent
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
return results;
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
//#endregion
|
|
944
|
+
//#region src/models/AnalyticsProperty.ts
|
|
985
945
|
var __defProp$1 = Object.defineProperty;
|
|
986
946
|
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
987
947
|
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
if (kind && result) __defProp$1(target, key, result);
|
|
993
|
-
return result;
|
|
948
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
949
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
950
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
951
|
+
return result;
|
|
994
952
|
};
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
resolvedPrompt.text,
|
|
1141
|
-
promptMessageOptions(resolvedPrompt.ai)
|
|
1142
|
-
);
|
|
1143
|
-
return {
|
|
1144
|
-
action: "analyzePerformance",
|
|
1145
|
-
period,
|
|
1146
|
-
analysis: analysis.trim()
|
|
1147
|
-
};
|
|
1148
|
-
}
|
|
1149
|
-
/**
|
|
1150
|
-
* AI-powered: Check if property is performing well
|
|
1151
|
-
*/
|
|
1152
|
-
async isPerformingWell() {
|
|
1153
|
-
return await this.is(
|
|
1154
|
-
`
|
|
953
|
+
var AnalyticsProperty = class extends SmrtObject {
|
|
954
|
+
tenantId = null;
|
|
955
|
+
/**
|
|
956
|
+
* Internal name/identifier
|
|
957
|
+
*/
|
|
958
|
+
name = "";
|
|
959
|
+
/**
|
|
960
|
+
* Human-readable display name
|
|
961
|
+
*/
|
|
962
|
+
displayName = "";
|
|
963
|
+
/**
|
|
964
|
+
* Analytics provider (ga4, plausible, matomo)
|
|
965
|
+
*/
|
|
966
|
+
provider = AnalyticsProvider.GA4;
|
|
967
|
+
/**
|
|
968
|
+
* External ID from the provider (e.g., "properties/123456789" for GA4,
|
|
969
|
+
* idSite for Matomo)
|
|
970
|
+
*/
|
|
971
|
+
externalId = "";
|
|
972
|
+
/**
|
|
973
|
+
* Measurement ID for GA4 (G-XXXXXXXXXX)
|
|
974
|
+
*/
|
|
975
|
+
measurementId = "";
|
|
976
|
+
apiSecret = "";
|
|
977
|
+
/**
|
|
978
|
+
* Site domain for Plausible/Matomo
|
|
979
|
+
*/
|
|
980
|
+
siteDomain = "";
|
|
981
|
+
/**
|
|
982
|
+
* Property timezone
|
|
983
|
+
*/
|
|
984
|
+
timeZone = "America/Los_Angeles";
|
|
985
|
+
/**
|
|
986
|
+
* Currency code (e.g., 'USD', 'EUR')
|
|
987
|
+
*/
|
|
988
|
+
currencyCode = "USD";
|
|
989
|
+
/**
|
|
990
|
+
* Industry category
|
|
991
|
+
*/
|
|
992
|
+
industryCategory = "";
|
|
993
|
+
/**
|
|
994
|
+
* Service level (STANDARD, PREMIUM)
|
|
995
|
+
*/
|
|
996
|
+
serviceLevel = "STANDARD";
|
|
997
|
+
/**
|
|
998
|
+
* Property status
|
|
999
|
+
*/
|
|
1000
|
+
status = AnalyticsPropertyStatus.ACTIVE;
|
|
1001
|
+
/**
|
|
1002
|
+
* Last sync timestamp with provider
|
|
1003
|
+
*/
|
|
1004
|
+
lastSyncAt = null;
|
|
1005
|
+
providerMetadata = "{}";
|
|
1006
|
+
constructor(options = {}) {
|
|
1007
|
+
super(options);
|
|
1008
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
1009
|
+
if (options.name !== void 0) this.name = options.name;
|
|
1010
|
+
if (options.displayName !== void 0) this.displayName = options.displayName;
|
|
1011
|
+
if (options.provider !== void 0) this.provider = options.provider;
|
|
1012
|
+
if (options.externalId !== void 0) this.externalId = options.externalId;
|
|
1013
|
+
if (options.measurementId !== void 0) this.measurementId = options.measurementId;
|
|
1014
|
+
if (options.apiSecret !== void 0) this.apiSecret = options.apiSecret;
|
|
1015
|
+
if (options.siteDomain !== void 0) this.siteDomain = options.siteDomain;
|
|
1016
|
+
if (options.timeZone !== void 0) this.timeZone = options.timeZone;
|
|
1017
|
+
if (options.currencyCode !== void 0) this.currencyCode = options.currencyCode;
|
|
1018
|
+
if (options.industryCategory !== void 0) this.industryCategory = options.industryCategory;
|
|
1019
|
+
if (options.serviceLevel !== void 0) this.serviceLevel = options.serviceLevel;
|
|
1020
|
+
if (options.status !== void 0) this.status = options.status;
|
|
1021
|
+
if (options.lastSyncAt !== void 0) this.lastSyncAt = options.lastSyncAt;
|
|
1022
|
+
if (options.providerMetadata !== void 0) this.providerMetadata = options.providerMetadata;
|
|
1023
|
+
}
|
|
1024
|
+
/**
|
|
1025
|
+
* Check if this is a GA4 property
|
|
1026
|
+
*/
|
|
1027
|
+
isGA4() {
|
|
1028
|
+
return this.provider === AnalyticsProvider.GA4;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Check if this is a Plausible site
|
|
1032
|
+
*/
|
|
1033
|
+
isPlausible() {
|
|
1034
|
+
return this.provider === AnalyticsProvider.PLAUSIBLE;
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Check if this is a Matomo site
|
|
1038
|
+
*/
|
|
1039
|
+
isMatomo() {
|
|
1040
|
+
return this.provider === AnalyticsProvider.MATOMO;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Get parsed provider metadata
|
|
1044
|
+
*/
|
|
1045
|
+
getProviderMetadata() {
|
|
1046
|
+
try {
|
|
1047
|
+
return JSON.parse(this.providerMetadata);
|
|
1048
|
+
} catch {
|
|
1049
|
+
return {};
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
/**
|
|
1053
|
+
* Set provider metadata
|
|
1054
|
+
*/
|
|
1055
|
+
setProviderMetadata(metadata) {
|
|
1056
|
+
this.providerMetadata = JSON.stringify(metadata);
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Mark as synced with provider
|
|
1060
|
+
*/
|
|
1061
|
+
markSynced() {
|
|
1062
|
+
this.lastSyncAt = /* @__PURE__ */ new Date();
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* AI-powered: Analyze property performance.
|
|
1066
|
+
*
|
|
1067
|
+
* Uses the `smrtAnalytics.property.analyzePerformance` prompt registered
|
|
1068
|
+
* via `@happyvertical/smrt-prompts`, allowing tenant- or instance-level
|
|
1069
|
+
* overrides of the template, model, and parameters at runtime.
|
|
1070
|
+
*
|
|
1071
|
+
* Only non-PII fields (display name, provider label, requested period)
|
|
1072
|
+
* are sent to the AI provider. Internal identifiers (`id`, `externalId`,
|
|
1073
|
+
* `measurementId`, `apiSecret`, `providerMetadata`) are intentionally
|
|
1074
|
+
* excluded — see `../prompts.ts` for the full exclusion rationale.
|
|
1075
|
+
*/
|
|
1076
|
+
async analyzePerformance(options = {}) {
|
|
1077
|
+
const period = options.period || "30 days";
|
|
1078
|
+
const db = this.options.db ?? this.options.persistence;
|
|
1079
|
+
const resolvedPrompt = await resolvePrompt(smrtAnalyticsAnalyzePerformancePrompt.key, {
|
|
1080
|
+
db,
|
|
1081
|
+
variables: {
|
|
1082
|
+
period,
|
|
1083
|
+
propertyDisplayName: this.displayName || "",
|
|
1084
|
+
propertyProvider: this.provider || ""
|
|
1085
|
+
}
|
|
1086
|
+
});
|
|
1087
|
+
return {
|
|
1088
|
+
action: "analyzePerformance",
|
|
1089
|
+
period,
|
|
1090
|
+
analysis: (await (await this.getAiClient()).message(resolvedPrompt.text, promptMessageOptions(resolvedPrompt.ai))).trim()
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* AI-powered: Check if property is performing well
|
|
1095
|
+
*/
|
|
1096
|
+
async isPerformingWell() {
|
|
1097
|
+
return await this.is(`
|
|
1155
1098
|
Based on the property configuration and metadata:
|
|
1156
1099
|
- Property: ${this.displayName}
|
|
1157
1100
|
- Provider: ${this.provider}
|
|
@@ -1159,586 +1102,550 @@ let AnalyticsProperty = class extends SmrtObject {
|
|
|
1159
1102
|
- Last sync: ${this.lastSyncAt}
|
|
1160
1103
|
|
|
1161
1104
|
Is this property properly configured and likely performing well?
|
|
1162
|
-
`,
|
|
1163
|
-
|
|
1164
|
-
{ includeData: false }
|
|
1165
|
-
);
|
|
1166
|
-
}
|
|
1105
|
+
`, { includeData: false });
|
|
1106
|
+
}
|
|
1167
1107
|
};
|
|
1168
|
-
__decorateClass$1([
|
|
1169
|
-
|
|
1170
|
-
], AnalyticsProperty.prototype, "
|
|
1171
|
-
__decorateClass$1([
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1108
|
+
__decorateClass$1([tenantId({ nullable: true })], AnalyticsProperty.prototype, "tenantId", 2);
|
|
1109
|
+
__decorateClass$1([field({ sensitive: true })], AnalyticsProperty.prototype, "apiSecret", 2);
|
|
1110
|
+
__decorateClass$1([field({ sensitive: true })], AnalyticsProperty.prototype, "providerMetadata", 2);
|
|
1111
|
+
AnalyticsProperty = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
|
|
1112
|
+
tableStrategy: "sti",
|
|
1113
|
+
api: { include: [
|
|
1114
|
+
"list",
|
|
1115
|
+
"get",
|
|
1116
|
+
"create",
|
|
1117
|
+
"update"
|
|
1118
|
+
] },
|
|
1119
|
+
mcp: { include: [
|
|
1120
|
+
"list",
|
|
1121
|
+
"get",
|
|
1122
|
+
"sync",
|
|
1123
|
+
"runReport"
|
|
1124
|
+
] },
|
|
1125
|
+
cli: true
|
|
1126
|
+
})], AnalyticsProperty);
|
|
1127
|
+
//#endregion
|
|
1128
|
+
//#region src/collections/AnalyticsPropertyCollection.ts
|
|
1129
|
+
var AnalyticsPropertyCollection = class extends SmrtCollection {
|
|
1130
|
+
static _itemClass = AnalyticsProperty;
|
|
1131
|
+
/**
|
|
1132
|
+
* Find property by external ID
|
|
1133
|
+
*
|
|
1134
|
+
* @param externalId - External ID from provider
|
|
1135
|
+
* @returns Matching property or null
|
|
1136
|
+
*/
|
|
1137
|
+
async findByExternalId(externalId) {
|
|
1138
|
+
const results = await this.list({
|
|
1139
|
+
where: { externalId },
|
|
1140
|
+
limit: 1
|
|
1141
|
+
});
|
|
1142
|
+
return results.length > 0 ? results[0] : null;
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Find property by measurement ID (GA4)
|
|
1146
|
+
*
|
|
1147
|
+
* @param measurementId - GA4 measurement ID (G-XXXXXXXXXX)
|
|
1148
|
+
* @returns Matching property or null
|
|
1149
|
+
*/
|
|
1150
|
+
async findByMeasurementId(measurementId) {
|
|
1151
|
+
const results = await this.list({
|
|
1152
|
+
where: { measurementId },
|
|
1153
|
+
limit: 1
|
|
1154
|
+
});
|
|
1155
|
+
return results.length > 0 ? results[0] : null;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Find property by site domain and optional provider.
|
|
1159
|
+
*
|
|
1160
|
+
* @param siteDomain - Provider site domain
|
|
1161
|
+
* @param provider - Optional provider discriminator for migration/coexistence
|
|
1162
|
+
* @returns Matching property or null
|
|
1163
|
+
*/
|
|
1164
|
+
async findBySiteDomain(siteDomain, provider) {
|
|
1165
|
+
const results = await this.list({
|
|
1166
|
+
where: provider ? {
|
|
1167
|
+
siteDomain,
|
|
1168
|
+
provider
|
|
1169
|
+
} : { siteDomain },
|
|
1170
|
+
limit: 1
|
|
1171
|
+
});
|
|
1172
|
+
return results.length > 0 ? results[0] : null;
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Find properties by provider
|
|
1176
|
+
*
|
|
1177
|
+
* @param provider - Analytics provider
|
|
1178
|
+
* @returns Array of matching properties
|
|
1179
|
+
*/
|
|
1180
|
+
async findByProvider(provider) {
|
|
1181
|
+
return await this.list({
|
|
1182
|
+
where: { provider },
|
|
1183
|
+
orderBy: "displayName ASC"
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Find all GA4 properties
|
|
1188
|
+
*/
|
|
1189
|
+
async findGA4Properties() {
|
|
1190
|
+
return await this.findByProvider(AnalyticsProvider.GA4);
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Find all Plausible sites
|
|
1194
|
+
*/
|
|
1195
|
+
async findPlausibleSites() {
|
|
1196
|
+
return await this.findByProvider(AnalyticsProvider.PLAUSIBLE);
|
|
1197
|
+
}
|
|
1198
|
+
/**
|
|
1199
|
+
* Find all Matomo sites
|
|
1200
|
+
*/
|
|
1201
|
+
async findMatomoSites() {
|
|
1202
|
+
return await this.findByProvider(AnalyticsProvider.MATOMO);
|
|
1203
|
+
}
|
|
1204
|
+
/**
|
|
1205
|
+
* Find properties by status
|
|
1206
|
+
*
|
|
1207
|
+
* @param status - Property status
|
|
1208
|
+
* @returns Array of matching properties
|
|
1209
|
+
*/
|
|
1210
|
+
async findByStatus(status) {
|
|
1211
|
+
return await this.list({
|
|
1212
|
+
where: { status },
|
|
1213
|
+
orderBy: "displayName ASC"
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Find all active properties
|
|
1218
|
+
*/
|
|
1219
|
+
async findActive() {
|
|
1220
|
+
return await this.findByStatus(AnalyticsPropertyStatus.ACTIVE);
|
|
1221
|
+
}
|
|
1222
|
+
/**
|
|
1223
|
+
* Find properties that need syncing (not synced in last N hours)
|
|
1224
|
+
*
|
|
1225
|
+
* @param hoursAgo - Hours since last sync
|
|
1226
|
+
* @returns Array of properties needing sync
|
|
1227
|
+
*/
|
|
1228
|
+
async findNeedingSync(hoursAgo = 24) {
|
|
1229
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1230
|
+
cutoff.setTime(cutoff.getTime() - hoursAgo * 60 * 60 * 1e3);
|
|
1231
|
+
return (await this.findActive()).filter((p) => !p.lastSyncAt || p.lastSyncAt < cutoff);
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
//#endregion
|
|
1235
|
+
//#region src/models/AnalyticsReport.ts
|
|
1289
1236
|
var __defProp = Object.defineProperty;
|
|
1290
1237
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
1291
1238
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
if (kind && result) __defProp(target, key, result);
|
|
1297
|
-
return result;
|
|
1239
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
1240
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1241
|
+
if (kind && result) __defProp(target, key, result);
|
|
1242
|
+
return result;
|
|
1298
1243
|
};
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
* word convention or the boolean will silently fall to `false`.
|
|
1578
|
-
*/
|
|
1579
|
-
async hasPositiveTrends() {
|
|
1580
|
-
const resultData = this.getResultData();
|
|
1581
|
-
const db = this.options.db ?? this.options.persistence;
|
|
1582
|
-
const resolvedPrompt = await resolvePrompt(
|
|
1583
|
-
smrtAnalyticsHasPositiveTrendsPrompt.key,
|
|
1584
|
-
{
|
|
1585
|
-
db,
|
|
1586
|
-
variables: {
|
|
1587
|
-
reportMetrics: this.metrics || "[]",
|
|
1588
|
-
reportData: JSON.stringify(resultData)
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
);
|
|
1592
|
-
const ai = await this.getAiClient();
|
|
1593
|
-
const response = (await ai.message(
|
|
1594
|
-
resolvedPrompt.text,
|
|
1595
|
-
promptMessageOptions(resolvedPrompt.ai)
|
|
1596
|
-
)).trim();
|
|
1597
|
-
return /^\s*(yes|true)\b/i.test(response);
|
|
1598
|
-
}
|
|
1244
|
+
var AnalyticsReport = class extends SmrtObject {
|
|
1245
|
+
tenantId = null;
|
|
1246
|
+
propertyId = "";
|
|
1247
|
+
/**
|
|
1248
|
+
* Report name
|
|
1249
|
+
*/
|
|
1250
|
+
name = "";
|
|
1251
|
+
/**
|
|
1252
|
+
* Report description
|
|
1253
|
+
*/
|
|
1254
|
+
description = "";
|
|
1255
|
+
/**
|
|
1256
|
+
* Dimensions to group by (JSON array)
|
|
1257
|
+
*/
|
|
1258
|
+
dimensions = "[]";
|
|
1259
|
+
/**
|
|
1260
|
+
* Metrics to retrieve (JSON array)
|
|
1261
|
+
*/
|
|
1262
|
+
metrics = "[]";
|
|
1263
|
+
/**
|
|
1264
|
+
* Date range start (relative or absolute)
|
|
1265
|
+
*/
|
|
1266
|
+
dateRangeStart = "7daysAgo";
|
|
1267
|
+
/**
|
|
1268
|
+
* Date range end (relative or absolute)
|
|
1269
|
+
*/
|
|
1270
|
+
dateRangeEnd = "today";
|
|
1271
|
+
/**
|
|
1272
|
+
* Dimension filter expression (JSON)
|
|
1273
|
+
*/
|
|
1274
|
+
dimensionFilter = "";
|
|
1275
|
+
/**
|
|
1276
|
+
* Metric filter expression (JSON)
|
|
1277
|
+
*/
|
|
1278
|
+
metricFilter = "";
|
|
1279
|
+
/**
|
|
1280
|
+
* Sort order (JSON array)
|
|
1281
|
+
*/
|
|
1282
|
+
orderBy = "[]";
|
|
1283
|
+
/**
|
|
1284
|
+
* Maximum results to return
|
|
1285
|
+
*/
|
|
1286
|
+
maxResults = 0;
|
|
1287
|
+
/**
|
|
1288
|
+
* Report status
|
|
1289
|
+
*/
|
|
1290
|
+
status = ReportStatus.DRAFT;
|
|
1291
|
+
/**
|
|
1292
|
+
* Scheduling frequency
|
|
1293
|
+
*/
|
|
1294
|
+
frequency = ReportFrequency.ONCE;
|
|
1295
|
+
/**
|
|
1296
|
+
* Last run timestamp
|
|
1297
|
+
*/
|
|
1298
|
+
lastRunAt = null;
|
|
1299
|
+
/**
|
|
1300
|
+
* Next scheduled run
|
|
1301
|
+
*/
|
|
1302
|
+
nextRunAt = null;
|
|
1303
|
+
/**
|
|
1304
|
+
* Cached result data (JSON)
|
|
1305
|
+
*/
|
|
1306
|
+
resultData = "";
|
|
1307
|
+
/**
|
|
1308
|
+
* Row count from last run
|
|
1309
|
+
*/
|
|
1310
|
+
rowCount = 0;
|
|
1311
|
+
/**
|
|
1312
|
+
* Error message from last failed run
|
|
1313
|
+
*/
|
|
1314
|
+
lastError = "";
|
|
1315
|
+
constructor(options = {}) {
|
|
1316
|
+
super(options);
|
|
1317
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
1318
|
+
if (options.propertyId !== void 0) this.propertyId = options.propertyId;
|
|
1319
|
+
if (options.name !== void 0) this.name = options.name;
|
|
1320
|
+
if (options.description !== void 0) this.description = options.description;
|
|
1321
|
+
if (options.dimensions !== void 0) this.dimensions = options.dimensions;
|
|
1322
|
+
if (options.metrics !== void 0) this.metrics = options.metrics;
|
|
1323
|
+
if (options.dateRangeStart !== void 0) this.dateRangeStart = options.dateRangeStart;
|
|
1324
|
+
if (options.dateRangeEnd !== void 0) this.dateRangeEnd = options.dateRangeEnd;
|
|
1325
|
+
if (options.dimensionFilter !== void 0) this.dimensionFilter = options.dimensionFilter;
|
|
1326
|
+
if (options.metricFilter !== void 0) this.metricFilter = options.metricFilter;
|
|
1327
|
+
if (options.orderBy !== void 0) this.orderBy = options.orderBy;
|
|
1328
|
+
if (options.maxResults !== void 0) this.maxResults = options.maxResults;
|
|
1329
|
+
if (options.status !== void 0) this.status = options.status;
|
|
1330
|
+
if (options.frequency !== void 0) this.frequency = options.frequency;
|
|
1331
|
+
if (options.lastRunAt !== void 0) this.lastRunAt = options.lastRunAt;
|
|
1332
|
+
if (options.nextRunAt !== void 0) this.nextRunAt = options.nextRunAt;
|
|
1333
|
+
if (options.resultData !== void 0) this.resultData = options.resultData;
|
|
1334
|
+
if (options.rowCount !== void 0) this.rowCount = options.rowCount;
|
|
1335
|
+
if (options.lastError !== void 0) this.lastError = options.lastError;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Get parsed dimensions
|
|
1339
|
+
*/
|
|
1340
|
+
getDimensions() {
|
|
1341
|
+
try {
|
|
1342
|
+
return JSON.parse(this.dimensions);
|
|
1343
|
+
} catch {
|
|
1344
|
+
return [];
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Set dimensions
|
|
1349
|
+
*/
|
|
1350
|
+
setDimensions(dimensions) {
|
|
1351
|
+
this.dimensions = JSON.stringify(dimensions);
|
|
1352
|
+
}
|
|
1353
|
+
/**
|
|
1354
|
+
* Get parsed metrics
|
|
1355
|
+
*/
|
|
1356
|
+
getMetrics() {
|
|
1357
|
+
try {
|
|
1358
|
+
return JSON.parse(this.metrics);
|
|
1359
|
+
} catch {
|
|
1360
|
+
return [];
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
/**
|
|
1364
|
+
* Set metrics
|
|
1365
|
+
*/
|
|
1366
|
+
setMetrics(metrics) {
|
|
1367
|
+
this.metrics = JSON.stringify(metrics);
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Get parsed result data
|
|
1371
|
+
*/
|
|
1372
|
+
getResultData() {
|
|
1373
|
+
if (!this.resultData) return null;
|
|
1374
|
+
try {
|
|
1375
|
+
return JSON.parse(this.resultData);
|
|
1376
|
+
} catch {
|
|
1377
|
+
return null;
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
/**
|
|
1381
|
+
* Set result data
|
|
1382
|
+
*/
|
|
1383
|
+
setResultData(data) {
|
|
1384
|
+
this.resultData = JSON.stringify(data);
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Mark report as running
|
|
1388
|
+
*/
|
|
1389
|
+
markRunning() {
|
|
1390
|
+
this.status = ReportStatus.RUNNING;
|
|
1391
|
+
this.lastError = "";
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Mark report as completed with results
|
|
1395
|
+
*/
|
|
1396
|
+
markCompleted(rowCount) {
|
|
1397
|
+
this.status = ReportStatus.COMPLETED;
|
|
1398
|
+
this.lastRunAt = /* @__PURE__ */ new Date();
|
|
1399
|
+
this.rowCount = rowCount;
|
|
1400
|
+
this.lastError = "";
|
|
1401
|
+
this.calculateNextRun();
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Mark report as failed
|
|
1405
|
+
*/
|
|
1406
|
+
markFailed(error) {
|
|
1407
|
+
this.status = ReportStatus.FAILED;
|
|
1408
|
+
this.lastRunAt = /* @__PURE__ */ new Date();
|
|
1409
|
+
this.lastError = error;
|
|
1410
|
+
this.calculateNextRun();
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Calculate next scheduled run based on frequency
|
|
1414
|
+
*/
|
|
1415
|
+
calculateNextRun() {
|
|
1416
|
+
if (this.frequency === ReportFrequency.ONCE) {
|
|
1417
|
+
this.nextRunAt = null;
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
const next = /* @__PURE__ */ new Date(/* @__PURE__ */ new Date());
|
|
1421
|
+
switch (this.frequency) {
|
|
1422
|
+
case ReportFrequency.DAILY:
|
|
1423
|
+
next.setDate(next.getDate() + 1);
|
|
1424
|
+
break;
|
|
1425
|
+
case ReportFrequency.WEEKLY:
|
|
1426
|
+
next.setDate(next.getDate() + 7);
|
|
1427
|
+
break;
|
|
1428
|
+
case ReportFrequency.MONTHLY:
|
|
1429
|
+
next.setMonth(next.getMonth() + 1);
|
|
1430
|
+
break;
|
|
1431
|
+
}
|
|
1432
|
+
this.nextRunAt = next;
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Check if report is due to run
|
|
1436
|
+
*/
|
|
1437
|
+
isDue() {
|
|
1438
|
+
if (this.frequency === ReportFrequency.ONCE) return this.status === ReportStatus.SCHEDULED && !this.lastRunAt;
|
|
1439
|
+
if (!this.nextRunAt) return false;
|
|
1440
|
+
return /* @__PURE__ */ new Date() >= this.nextRunAt;
|
|
1441
|
+
}
|
|
1442
|
+
/**
|
|
1443
|
+
* AI-powered: Analyze report results.
|
|
1444
|
+
*
|
|
1445
|
+
* Uses the `smrtAnalytics.report.analyzeResults` prompt registered via
|
|
1446
|
+
* `@happyvertical/smrt-prompts`, allowing tenant- or instance-level
|
|
1447
|
+
* overrides of the template, model, and parameters at runtime.
|
|
1448
|
+
*
|
|
1449
|
+
* Internal identifiers (`id`, `propertyId`, `tenantId`, `lastError`, raw
|
|
1450
|
+
* `dimensionFilter` / `metricFilter` JSON) are excluded from the prompt
|
|
1451
|
+
* variables — see `../prompts.ts` for the exclusion rationale.
|
|
1452
|
+
*
|
|
1453
|
+
* **`resultData` is FORWARDED VERBATIM.** The persisted result rows are
|
|
1454
|
+
* JSON-stringified into the `reportData` variable; this package cannot
|
|
1455
|
+
* strip PII because the row schema is determined by which dimensions /
|
|
1456
|
+
* metrics the caller asked the analytics provider to return. If the
|
|
1457
|
+
* persisted rows contain `userPseudoId`, `clientId`, IP-derived
|
|
1458
|
+
* geolocation, or any other identifier, those fields WILL reach the AI
|
|
1459
|
+
* provider. Callers are responsible for excluding PII-bearing dimensions
|
|
1460
|
+
* before persisting, applying a column allowlist at the call site, or
|
|
1461
|
+
* overriding the prompt template via `PromptOverride`. The forwarding is
|
|
1462
|
+
* pinned by a regression test in
|
|
1463
|
+
* `__tests__/analytics-report-prompt.test.ts`.
|
|
1464
|
+
*
|
|
1465
|
+
* The previous implementation issued a second freeform `this.do()` call
|
|
1466
|
+
* to re-summarize "top 3 insights"; that behaviour is now folded into
|
|
1467
|
+
* the single registered template (which already asks for findings,
|
|
1468
|
+
* trends, and recommendations) — `insights` mirrors `analysis` so the
|
|
1469
|
+
* return shape is preserved without a redundant AI round-trip.
|
|
1470
|
+
*/
|
|
1471
|
+
async analyzeResults(_options = {}) {
|
|
1472
|
+
const resultData = this.getResultData();
|
|
1473
|
+
const db = this.options.db ?? this.options.persistence;
|
|
1474
|
+
const resolvedPrompt = await resolvePrompt(smrtAnalyticsAnalyzeResultsPrompt.key, {
|
|
1475
|
+
db,
|
|
1476
|
+
variables: {
|
|
1477
|
+
reportName: this.name || "",
|
|
1478
|
+
reportDimensions: this.dimensions || "[]",
|
|
1479
|
+
reportMetrics: this.metrics || "[]",
|
|
1480
|
+
dateRangeStart: this.dateRangeStart || "",
|
|
1481
|
+
dateRangeEnd: this.dateRangeEnd || "",
|
|
1482
|
+
rowCount: String(this.rowCount),
|
|
1483
|
+
reportData: JSON.stringify(resultData, null, 2)
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
const analysis = (await (await this.getAiClient()).message(resolvedPrompt.text, promptMessageOptions(resolvedPrompt.ai))).trim();
|
|
1487
|
+
return {
|
|
1488
|
+
action: "analyzeResults",
|
|
1489
|
+
analysis,
|
|
1490
|
+
insights: analysis
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
/**
|
|
1494
|
+
* AI-powered: Check if results show positive trends.
|
|
1495
|
+
*
|
|
1496
|
+
* Uses the `smrtAnalytics.report.hasPositiveTrends` prompt registered
|
|
1497
|
+
* via `@happyvertical/smrt-prompts`. Only the metric labels and the
|
|
1498
|
+
* aggregate `resultData` JSON are sent to the AI provider — though as
|
|
1499
|
+
* with `analyzeResults`, `resultData` is forwarded verbatim and may
|
|
1500
|
+
* carry PII the caller persisted; see `analyzeResults` docstring and
|
|
1501
|
+
* `../prompts.ts`.
|
|
1502
|
+
*
|
|
1503
|
+
* Boolean coercion uses `/^\s*(yes|true)\b/i` against the trimmed
|
|
1504
|
+
* response. The registered prompt template explicitly instructs the
|
|
1505
|
+
* model to begin its answer with the literal word "yes" or "no" so
|
|
1506
|
+
* this regex is reliable; tenant overrides MUST preserve that leading-
|
|
1507
|
+
* word convention or the boolean will silently fall to `false`.
|
|
1508
|
+
*/
|
|
1509
|
+
async hasPositiveTrends() {
|
|
1510
|
+
const resultData = this.getResultData();
|
|
1511
|
+
const db = this.options.db ?? this.options.persistence;
|
|
1512
|
+
const resolvedPrompt = await resolvePrompt(smrtAnalyticsHasPositiveTrendsPrompt.key, {
|
|
1513
|
+
db,
|
|
1514
|
+
variables: {
|
|
1515
|
+
reportMetrics: this.metrics || "[]",
|
|
1516
|
+
reportData: JSON.stringify(resultData)
|
|
1517
|
+
}
|
|
1518
|
+
});
|
|
1519
|
+
const response = (await (await this.getAiClient()).message(resolvedPrompt.text, promptMessageOptions(resolvedPrompt.ai))).trim();
|
|
1520
|
+
return /^\s*(yes|true)\b/i.test(response);
|
|
1521
|
+
}
|
|
1599
1522
|
};
|
|
1600
|
-
__decorateClass([
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
AnalyticsEvent,
|
|
1725
|
-
AnalyticsEventCollection,
|
|
1726
|
-
AnalyticsProperty,
|
|
1727
|
-
AnalyticsPropertyCollection,
|
|
1728
|
-
AnalyticsPropertyStatus,
|
|
1729
|
-
AnalyticsProvider,
|
|
1730
|
-
AnalyticsReport,
|
|
1731
|
-
AnalyticsReportCollection,
|
|
1732
|
-
CountingMethod,
|
|
1733
|
-
CustomDimensionScope,
|
|
1734
|
-
DataStreamStatus,
|
|
1735
|
-
DataStreamType,
|
|
1736
|
-
ReportFrequency,
|
|
1737
|
-
ReportStatus,
|
|
1738
|
-
TrackingEventStatus,
|
|
1739
|
-
promptMessageOptions,
|
|
1740
|
-
smrtAnalyticsAnalyzePerformancePrompt,
|
|
1741
|
-
smrtAnalyticsAnalyzeResultsPrompt,
|
|
1742
|
-
smrtAnalyticsHasPositiveTrendsPrompt
|
|
1523
|
+
__decorateClass([tenantId({ nullable: true })], AnalyticsReport.prototype, "tenantId", 2);
|
|
1524
|
+
__decorateClass([foreignKey("AnalyticsProperty")], AnalyticsReport.prototype, "propertyId", 2);
|
|
1525
|
+
AnalyticsReport = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
1526
|
+
tableStrategy: "sti",
|
|
1527
|
+
api: { include: [
|
|
1528
|
+
"list",
|
|
1529
|
+
"get",
|
|
1530
|
+
"create",
|
|
1531
|
+
"update",
|
|
1532
|
+
"run"
|
|
1533
|
+
] },
|
|
1534
|
+
mcp: { include: [
|
|
1535
|
+
"list",
|
|
1536
|
+
"get",
|
|
1537
|
+
"run",
|
|
1538
|
+
"analyze"
|
|
1539
|
+
] },
|
|
1540
|
+
cli: true
|
|
1541
|
+
})], AnalyticsReport);
|
|
1542
|
+
//#endregion
|
|
1543
|
+
//#region src/collections/AnalyticsReportCollection.ts
|
|
1544
|
+
var AnalyticsReportCollection = class extends SmrtCollection {
|
|
1545
|
+
static _itemClass = AnalyticsReport;
|
|
1546
|
+
/**
|
|
1547
|
+
* Find reports by property
|
|
1548
|
+
*
|
|
1549
|
+
* @param propertyId - Parent property ID
|
|
1550
|
+
* @returns Array of reports
|
|
1551
|
+
*/
|
|
1552
|
+
async findByProperty(propertyId) {
|
|
1553
|
+
return await this.list({
|
|
1554
|
+
where: { propertyId },
|
|
1555
|
+
orderBy: "name ASC"
|
|
1556
|
+
});
|
|
1557
|
+
}
|
|
1558
|
+
/**
|
|
1559
|
+
* Find reports by status
|
|
1560
|
+
*
|
|
1561
|
+
* @param status - Report status
|
|
1562
|
+
* @returns Array of matching reports
|
|
1563
|
+
*/
|
|
1564
|
+
async findByStatus(status) {
|
|
1565
|
+
return await this.list({
|
|
1566
|
+
where: { status },
|
|
1567
|
+
orderBy: "name ASC"
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* Find all draft reports
|
|
1572
|
+
*/
|
|
1573
|
+
async findDrafts() {
|
|
1574
|
+
return await this.findByStatus(ReportStatus.DRAFT);
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Find all scheduled reports
|
|
1578
|
+
*/
|
|
1579
|
+
async findScheduled() {
|
|
1580
|
+
return await this.findByStatus(ReportStatus.SCHEDULED);
|
|
1581
|
+
}
|
|
1582
|
+
/**
|
|
1583
|
+
* Find all completed reports
|
|
1584
|
+
*/
|
|
1585
|
+
async findCompleted() {
|
|
1586
|
+
return await this.findByStatus(ReportStatus.COMPLETED);
|
|
1587
|
+
}
|
|
1588
|
+
/**
|
|
1589
|
+
* Find all failed reports
|
|
1590
|
+
*/
|
|
1591
|
+
async findFailed() {
|
|
1592
|
+
return await this.findByStatus(ReportStatus.FAILED);
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Find reports by frequency
|
|
1596
|
+
*
|
|
1597
|
+
* @param frequency - Report frequency
|
|
1598
|
+
* @returns Array of matching reports
|
|
1599
|
+
*/
|
|
1600
|
+
async findByFrequency(frequency) {
|
|
1601
|
+
return await this.list({
|
|
1602
|
+
where: { frequency },
|
|
1603
|
+
orderBy: "name ASC"
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Find all recurring reports (not one-time)
|
|
1608
|
+
*/
|
|
1609
|
+
async findRecurring() {
|
|
1610
|
+
return (await this.list({ orderBy: "name ASC" })).filter((r) => r.frequency !== ReportFrequency.ONCE);
|
|
1611
|
+
}
|
|
1612
|
+
/**
|
|
1613
|
+
* Find reports due to run
|
|
1614
|
+
*
|
|
1615
|
+
* @returns Array of reports that are due
|
|
1616
|
+
*/
|
|
1617
|
+
async findDue() {
|
|
1618
|
+
return (await this.list({ where: { status: ReportStatus.SCHEDULED } })).filter((r) => r.isDue());
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* Find reports for a property by status
|
|
1622
|
+
*
|
|
1623
|
+
* @param propertyId - Parent property ID
|
|
1624
|
+
* @param status - Report status
|
|
1625
|
+
* @returns Array of matching reports
|
|
1626
|
+
*/
|
|
1627
|
+
async findByPropertyAndStatus(propertyId, status) {
|
|
1628
|
+
return await this.list({
|
|
1629
|
+
where: {
|
|
1630
|
+
propertyId,
|
|
1631
|
+
status
|
|
1632
|
+
},
|
|
1633
|
+
orderBy: "name ASC"
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
/**
|
|
1637
|
+
* Find recently run reports
|
|
1638
|
+
*
|
|
1639
|
+
* @param hoursAgo - Hours since last run
|
|
1640
|
+
* @returns Array of recently run reports
|
|
1641
|
+
*/
|
|
1642
|
+
async findRecentlyRun(hoursAgo = 24) {
|
|
1643
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
1644
|
+
cutoff.setTime(cutoff.getTime() - hoursAgo * 60 * 60 * 1e3);
|
|
1645
|
+
return (await this.list({ orderBy: "lastRunAt DESC" })).filter((r) => r.lastRunAt && r.lastRunAt >= cutoff);
|
|
1646
|
+
}
|
|
1743
1647
|
};
|
|
1744
|
-
//#
|
|
1648
|
+
//#endregion
|
|
1649
|
+
export { AnalyticsDataStream, AnalyticsDataStreamCollection, AnalyticsEvent, AnalyticsEventCollection, AnalyticsProperty, AnalyticsPropertyCollection, AnalyticsPropertyStatus, AnalyticsProvider, AnalyticsReport, AnalyticsReportCollection, CountingMethod, CustomDimensionScope, DataStreamStatus, DataStreamType, ReportFrequency, ReportStatus, TrackingEventStatus, promptMessageOptions, smrtAnalyticsAnalyzePerformancePrompt, smrtAnalyticsAnalyzeResultsPrompt, smrtAnalyticsHasPositiveTrendsPrompt };
|
|
1650
|
+
|
|
1651
|
+
//# sourceMappingURL=index.js.map
|