make-mp-data 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/defaults.js +974 -0
- package/index.js +303 -152
- package/package.json +3 -3
- package/schemas/complex.js +14 -14
- package/schemas/funnels.js +66 -67
- package/schemas/simple.js +14 -4
- package/scratch.mjs +7 -4
- package/tests/unit.test.js +110 -14
- package/types.d.ts +24 -4
- package/utils.js +176 -41
package/schemas/complex.js
CHANGED
|
@@ -28,7 +28,7 @@ const config = {
|
|
|
28
28
|
"event": "checkout",
|
|
29
29
|
"weight": 2,
|
|
30
30
|
"properties": {
|
|
31
|
-
amount: weightedRange(5, 500, .25
|
|
31
|
+
amount: weightedRange(5, 500, .25),
|
|
32
32
|
currency: ["USD", "USD", "USD", "CAD", "EUR", "EUR", "BTC", "BTC", "ETH", "JPY"],
|
|
33
33
|
cart: makeProducts(12),
|
|
34
34
|
}
|
|
@@ -37,9 +37,9 @@ const config = {
|
|
|
37
37
|
"event": "add to cart",
|
|
38
38
|
"weight": 4,
|
|
39
39
|
"properties": {
|
|
40
|
-
amount: weightedRange(5, 500, .25
|
|
40
|
+
amount: weightedRange(5, 500, .25),
|
|
41
41
|
qty: integer(1, 5),
|
|
42
|
-
product_id: weightedRange(1, 1000, 1.4
|
|
42
|
+
product_id: weightedRange(1, 1000, 1.4)
|
|
43
43
|
}
|
|
44
44
|
},
|
|
45
45
|
{
|
|
@@ -56,10 +56,10 @@ const config = {
|
|
|
56
56
|
"properties": {
|
|
57
57
|
category: ["funny", "educational", "inspirational", "music", "news", "sports", "cooking", "DIY", "travel", "gaming"],
|
|
58
58
|
hashTags: makeHashTags,
|
|
59
|
-
watchTimeSec: weightedRange(10, 600
|
|
59
|
+
watchTimeSec: weightedRange(10, 600, .25,),
|
|
60
60
|
quality: ["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p"],
|
|
61
61
|
format: ["mp4", "avi", "mov", "mpg"],
|
|
62
|
-
video_id: weightedRange(1, 50000, 1.4
|
|
62
|
+
video_id: weightedRange(1, 50000, 1.4),
|
|
63
63
|
|
|
64
64
|
}
|
|
65
65
|
},
|
|
@@ -67,8 +67,8 @@ const config = {
|
|
|
67
67
|
"event": "comment",
|
|
68
68
|
"weight": 2,
|
|
69
69
|
"properties": {
|
|
70
|
-
length: weightedRange(1, 500, .25
|
|
71
|
-
video_id: weightedRange(1, 50000, 1.4
|
|
70
|
+
length: weightedRange(1, 500, .25),
|
|
71
|
+
video_id: weightedRange(1, 50000, 1.4),
|
|
72
72
|
has_replies: [true, false, false, false, false],
|
|
73
73
|
has_photo: [true, false, false, false, false],
|
|
74
74
|
|
|
@@ -78,7 +78,7 @@ const config = {
|
|
|
78
78
|
"event": "save video",
|
|
79
79
|
"weight": 4,
|
|
80
80
|
"properties": {
|
|
81
|
-
video_id: weightedRange(1, 50000, 1.4
|
|
81
|
+
video_id: weightedRange(1, 50000, 1.4),
|
|
82
82
|
ui_control: ["toolbar", "menu", "keyboard"]
|
|
83
83
|
|
|
84
84
|
|
|
@@ -88,7 +88,7 @@ const config = {
|
|
|
88
88
|
"event": "view item",
|
|
89
89
|
"weight": 8,
|
|
90
90
|
"properties": {
|
|
91
|
-
product_id: weightedRange(1,
|
|
91
|
+
product_id: weightedRange(1, 24, 3),
|
|
92
92
|
colors: ["light", "dark", "custom", "dark"]
|
|
93
93
|
}
|
|
94
94
|
},
|
|
@@ -96,7 +96,7 @@ const config = {
|
|
|
96
96
|
"event": "save item",
|
|
97
97
|
"weight": 5,
|
|
98
98
|
"properties": {
|
|
99
|
-
product_id: weightedRange(1, 1000, 12
|
|
99
|
+
product_id: weightedRange(1, 1000, 12),
|
|
100
100
|
colors: ["light", "dark", "custom", "dark"]
|
|
101
101
|
}
|
|
102
102
|
},
|
|
@@ -104,7 +104,7 @@ const config = {
|
|
|
104
104
|
"event": "support ticket",
|
|
105
105
|
"weight": 2,
|
|
106
106
|
"properties": {
|
|
107
|
-
product_id: weightedRange(1, 1000, .6
|
|
107
|
+
product_id: weightedRange(1, 1000, .6),
|
|
108
108
|
description: chance.sentence.bind(chance),
|
|
109
109
|
severity: ["low", "medium", "high"],
|
|
110
110
|
ticket_id: chance.guid.bind(chance)
|
|
@@ -144,8 +144,8 @@ const config = {
|
|
|
144
144
|
/** each generates it's own table */
|
|
145
145
|
scdProps: {
|
|
146
146
|
plan: ["free", "free", "free", "free", "basic", "basic", "basic", "premium", "premium", "enterprise"],
|
|
147
|
-
MRR: weightedRange(0, 10000,
|
|
148
|
-
NPS: weightedRange(0, 10,
|
|
147
|
+
MRR: weightedRange(0, 10000, .15),
|
|
148
|
+
NPS: weightedRange(0, 10, 2, 150),
|
|
149
149
|
subscribed: [true, true, true, true, true, true, false, false, false, false, "it's complicated"],
|
|
150
150
|
renewalDate: date(100, false),
|
|
151
151
|
},
|
|
@@ -155,7 +155,7 @@ const config = {
|
|
|
155
155
|
profit: { events: ["checkout"], values: [4, 2, 42, 420] },
|
|
156
156
|
watchTimeSec: {
|
|
157
157
|
events: ["watch video"],
|
|
158
|
-
values: weightedRange(50, 1200, 6
|
|
158
|
+
values: weightedRange(50, 1200, 6, 247)
|
|
159
159
|
}
|
|
160
160
|
},
|
|
161
161
|
|
package/schemas/funnels.js
CHANGED
|
@@ -37,7 +37,7 @@ const config = {
|
|
|
37
37
|
event: "checkout",
|
|
38
38
|
weight: 2,
|
|
39
39
|
properties: {
|
|
40
|
-
amount: weightedRange(5, 500, .25
|
|
40
|
+
amount: weightedRange(5, 500, .25),
|
|
41
41
|
currency: ["USD", "CAD", "EUR", "BTC", "ETH", "JPY"],
|
|
42
42
|
coupon: ["none", "none", "none", "none", "10%OFF", "20%OFF", "10%OFF", "20%OFF", "30%OFF", "40%OFF", "50%OFF"],
|
|
43
43
|
numItems: weightedRange(1, 10),
|
|
@@ -48,7 +48,7 @@ const config = {
|
|
|
48
48
|
event: "add to cart",
|
|
49
49
|
weight: 4,
|
|
50
50
|
properties: {
|
|
51
|
-
amount: weightedRange(5, 500, .25
|
|
51
|
+
amount: weightedRange(5, 500, .25),
|
|
52
52
|
rating: weightedRange(1, 5),
|
|
53
53
|
reviews: weightedRange(0, 35),
|
|
54
54
|
isFeaturedItem: [true, false, false],
|
|
@@ -71,7 +71,7 @@ const config = {
|
|
|
71
71
|
properties: {
|
|
72
72
|
videoCategory: pickAWinner(videoCategories, integer(0, 9)),
|
|
73
73
|
isFeaturedItem: [true, false, false],
|
|
74
|
-
watchTimeSec: weightedRange(10, 600, .25
|
|
74
|
+
watchTimeSec: weightedRange(10, 600, .25),
|
|
75
75
|
quality: ["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p"],
|
|
76
76
|
format: ["mp4", "avi", "mov", "mpg"],
|
|
77
77
|
uploader_id: chance.guid.bind(chance)
|
|
@@ -107,69 +107,68 @@ const config = {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
],
|
|
110
|
-
funnels: [
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
110
|
+
funnels: [
|
|
111
|
+
// {
|
|
112
|
+
// sequence: ["page view", "view item", "page view", "sign up"],
|
|
113
|
+
// weight: 1,
|
|
114
|
+
// isFirstFunnel: true,
|
|
115
|
+
// order: "sequential",
|
|
116
|
+
// conversionRate: 50,
|
|
117
|
+
// timeToConvert: 2,
|
|
118
|
+
// props: {
|
|
119
|
+
// variants: ["A", "B", "C", "Control"],
|
|
120
|
+
// flows: ["new", "existing", "loyal", "churned"],
|
|
121
|
+
// flags: ["on", "off"],
|
|
122
|
+
// experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
|
|
123
|
+
// multiVariate: [true, false]
|
|
124
|
+
|
|
125
|
+
// },
|
|
123
126
|
|
|
124
|
-
},
|
|
125
|
-
|
|
126
|
-
// isInterruptedFunnel: false, // an interrupted funnel will have random events interspersed with the sequence
|
|
127
|
-
// fixedTimeFunnel: 30, // if set this funnel will occur for all users at the same time ['cart charged', 'charge complete']
|
|
128
|
-
// churn: {
|
|
129
|
-
// isChurnFunnel: true, //if the user completes this funnel, they churn
|
|
130
|
-
// probabilityToReturn: 0.1, //if the user churns, this is the probability they will return
|
|
131
127
|
// },
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
sequence: ["
|
|
172
|
-
|
|
128
|
+
// {
|
|
129
|
+
// sequence: ["app install", "app open", "tutorial", "sign up"],
|
|
130
|
+
// weight: 1,
|
|
131
|
+
// isFirstFunnel: true,
|
|
132
|
+
// order: "sequential",
|
|
133
|
+
// conversionRate: 50,
|
|
134
|
+
// timeToConvert: 2,
|
|
135
|
+
// props: {
|
|
136
|
+
// variants: ["A", "B", "C", "Control"],
|
|
137
|
+
// flows: ["new", "existing", "loyal", "churned"],
|
|
138
|
+
// flags: ["on", "off"],
|
|
139
|
+
// experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
|
|
140
|
+
// multiVariate: [true, false]
|
|
141
|
+
|
|
142
|
+
// }
|
|
143
|
+
// },
|
|
144
|
+
// {
|
|
145
|
+
// sequence: ["view item", "add to cart", "checkout", "rage", "cage", "mage"],
|
|
146
|
+
// order: "interrupted"
|
|
147
|
+
// },
|
|
148
|
+
// {
|
|
149
|
+
// sequence: ["page view", "view item", "add to cart", "add to cart", "checkout"],
|
|
150
|
+
// weight: 3,
|
|
151
|
+
// isFirstFunnel: false,
|
|
152
|
+
// order: "sequential",
|
|
153
|
+
// conversionRate: 70,
|
|
154
|
+
// timeToConvert: 7 * 24,
|
|
155
|
+
// props: {
|
|
156
|
+
// variants: ["A", "B", "C", "Control"],
|
|
157
|
+
// flows: ["new", "existing", "loyal", "churned"],
|
|
158
|
+
// flags: ["on", "off"],
|
|
159
|
+
// experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
|
|
160
|
+
// multiVariate: [true, false]
|
|
161
|
+
|
|
162
|
+
// }
|
|
163
|
+
// },
|
|
164
|
+
// {
|
|
165
|
+
// timeToConvert: 2,
|
|
166
|
+
// conversionRate: 66,
|
|
167
|
+
// sequence: ["foo", "bar", "baz", "qux"],
|
|
168
|
+
// }, {
|
|
169
|
+
// weight: 4,
|
|
170
|
+
// sequence: ["video", "video", "attack", "defend", "click"],
|
|
171
|
+
// }
|
|
173
172
|
],
|
|
174
173
|
superProps: {
|
|
175
174
|
platform: ["web", "mobile", "web", "mobile", "web", "web", "kiosk", "smartTV"],
|
|
@@ -189,14 +188,14 @@ const config = {
|
|
|
189
188
|
|
|
190
189
|
scdProps: {
|
|
191
190
|
nps: [1, 1, 1, 4, 4, 4, 5, 5, 6, 7, 8, 9],
|
|
192
|
-
mrr: () => { weightedRange(10, 1000
|
|
191
|
+
mrr: () => { weightedRange(10, 1000, .25); },
|
|
193
192
|
},
|
|
194
193
|
mirrorProps: {
|
|
195
194
|
isBot: { events: "*", values: [false, false, false, false, true] },
|
|
196
|
-
profit: { events: ["checkout"], values: [4, 2, 42
|
|
195
|
+
profit: { events: ["checkout"], values: [4, 2, 42] },
|
|
197
196
|
watchTimeSec: {
|
|
198
197
|
events: ["watch video"],
|
|
199
|
-
values: weightedRange(50, 1200, 6
|
|
198
|
+
values: weightedRange(50, 1200, 6)
|
|
200
199
|
}
|
|
201
200
|
|
|
202
201
|
},
|
package/schemas/simple.js
CHANGED
|
@@ -31,13 +31,23 @@ const config = {
|
|
|
31
31
|
region: "US",
|
|
32
32
|
anonIds: false, //if true, anonymousIds are created for each user
|
|
33
33
|
sessionIds: false, //if true, sessionIds are created for each user
|
|
34
|
+
hasAdSpend: false,
|
|
35
|
+
|
|
36
|
+
hasLocation: true,
|
|
37
|
+
hasAndroidDevices: true,
|
|
38
|
+
hasIOSDevices: true,
|
|
39
|
+
hasDesktopDevices: true,
|
|
40
|
+
hasBrowser: true,
|
|
41
|
+
hasCampaigns: true,
|
|
42
|
+
isAnonymous: false,
|
|
43
|
+
|
|
34
44
|
|
|
35
45
|
events: [
|
|
36
46
|
{
|
|
37
47
|
event: "checkout",
|
|
38
48
|
weight: 2,
|
|
39
49
|
properties: {
|
|
40
|
-
amount: weightedRange(5, 500, .25
|
|
50
|
+
amount: weightedRange(5, 500, .25),
|
|
41
51
|
currency: ["USD", "CAD", "EUR", "BTC", "ETH", "JPY"],
|
|
42
52
|
coupon: ["none", "none", "none", "none", "10%OFF", "20%OFF", "10%OFF", "20%OFF", "30%OFF", "40%OFF", "50%OFF"],
|
|
43
53
|
numItems: weightedRange(1, 10),
|
|
@@ -48,7 +58,7 @@ const config = {
|
|
|
48
58
|
event: "add to cart",
|
|
49
59
|
weight: 4,
|
|
50
60
|
properties: {
|
|
51
|
-
amount: weightedRange(5, 500, .25
|
|
61
|
+
amount: weightedRange(5, 500, .25),
|
|
52
62
|
rating: weightedRange(1, 5),
|
|
53
63
|
reviews: weightedRange(0, 35),
|
|
54
64
|
isFeaturedItem: [true, false, false],
|
|
@@ -71,7 +81,7 @@ const config = {
|
|
|
71
81
|
properties: {
|
|
72
82
|
videoCategory: pickAWinner(videoCategories, integer(0, 9)),
|
|
73
83
|
isFeaturedItem: [true, false, false],
|
|
74
|
-
watchTimeSec: weightedRange(10, 600, .25
|
|
84
|
+
watchTimeSec: weightedRange(10, 600, .25),
|
|
75
85
|
quality: ["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p"],
|
|
76
86
|
format: ["mp4", "avi", "mov", "mpg"],
|
|
77
87
|
uploader_id: chance.guid.bind(chance)
|
|
@@ -133,7 +143,7 @@ const config = {
|
|
|
133
143
|
profit: { events: ["checkout"], values: [4, 2, 42, 420] },
|
|
134
144
|
watchTimeSec: {
|
|
135
145
|
events: ["watch video"],
|
|
136
|
-
values: weightedRange(50, 1200, 6
|
|
146
|
+
values: weightedRange(50, 1200, 6)
|
|
137
147
|
}
|
|
138
148
|
|
|
139
149
|
},
|
package/scratch.mjs
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import main from "./index.js";
|
|
2
2
|
import amir from './customers/amir.js';
|
|
3
3
|
import simple from './schemas/simple.js';
|
|
4
|
+
import funnels from './schemas/funnels.js';
|
|
5
|
+
import foobar from './schemas/foobar.js';
|
|
6
|
+
import complex from './schemas/complex.js';
|
|
7
|
+
import deepNest from './schemas/deepNest.js';
|
|
4
8
|
import execSync from 'child_process';
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
/** @type {main.Config} */
|
|
8
12
|
const spec = {
|
|
9
|
-
...
|
|
10
|
-
numUsers: 1000,
|
|
11
|
-
numEvents: 100000,
|
|
13
|
+
...simple,
|
|
12
14
|
writeToDisk: false,
|
|
13
15
|
verbose: true,
|
|
14
|
-
makeChart:
|
|
16
|
+
makeChart: false,
|
|
17
|
+
token: "e98e6af94f6ddfb5e967fa265484539a"
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
|
package/tests/unit.test.js
CHANGED
|
@@ -39,7 +39,12 @@ const { applySkew,
|
|
|
39
39
|
buildFileNames,
|
|
40
40
|
TimeSoup,
|
|
41
41
|
getChance,
|
|
42
|
-
initChance
|
|
42
|
+
initChance,
|
|
43
|
+
validateEventConfig,
|
|
44
|
+
validateTime,
|
|
45
|
+
interruptArray,
|
|
46
|
+
optimizedBoxMuller,
|
|
47
|
+
inferFunnels
|
|
43
48
|
} = require('../utils');
|
|
44
49
|
|
|
45
50
|
|
|
@@ -59,7 +64,7 @@ describe('timesoup', () => {
|
|
|
59
64
|
});
|
|
60
65
|
|
|
61
66
|
|
|
62
|
-
describe('
|
|
67
|
+
describe('names', () => {
|
|
63
68
|
|
|
64
69
|
test('default config', () => {
|
|
65
70
|
const config = { simulationName: 'testSim' };
|
|
@@ -182,7 +187,7 @@ describe('naming things', () => {
|
|
|
182
187
|
});
|
|
183
188
|
|
|
184
189
|
|
|
185
|
-
describe('
|
|
190
|
+
describe('determinism', () => {
|
|
186
191
|
test('initializes RNG with seed from environment variable', () => {
|
|
187
192
|
process.env.SEED = 'test-seed';
|
|
188
193
|
// @ts-ignore
|
|
@@ -206,8 +211,8 @@ describe('determined random', () => {
|
|
|
206
211
|
});
|
|
207
212
|
|
|
208
213
|
|
|
209
|
-
describe('
|
|
210
|
-
test('
|
|
214
|
+
describe('generation', () => {
|
|
215
|
+
test('users: can make', () => {
|
|
211
216
|
const numDays = 30;
|
|
212
217
|
const user = generateUser('uuid-123', numDays);
|
|
213
218
|
expect(user).toHaveProperty('distinct_id');
|
|
@@ -219,7 +224,7 @@ describe('generateUser', () => {
|
|
|
219
224
|
expect(user).toHaveProperty('sessionIds');
|
|
220
225
|
});
|
|
221
226
|
|
|
222
|
-
test('
|
|
227
|
+
test('user: in time range', () => {
|
|
223
228
|
const numDays = 30;
|
|
224
229
|
const user = generateUser('uuid-123', numDays);
|
|
225
230
|
const createdDate = dayjs(user.created, 'YYYY-MM-DD');
|
|
@@ -228,9 +233,86 @@ describe('generateUser', () => {
|
|
|
228
233
|
});
|
|
229
234
|
});
|
|
230
235
|
|
|
236
|
+
describe('validation', () => {
|
|
231
237
|
|
|
238
|
+
beforeAll(() => {
|
|
239
|
+
global.NOW = 1672531200; // fixed point in time for testing
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('events: throws non array', () => {
|
|
243
|
+
// @ts-ignore
|
|
244
|
+
expect(() => validateEventConfig("not an array")).toThrow("events must be an array");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test('events: strings', () => {
|
|
248
|
+
const events = ["event1", "event2"];
|
|
249
|
+
const result = validateEventConfig(events);
|
|
250
|
+
|
|
251
|
+
expect(result).toEqual([
|
|
252
|
+
{ event: "event1", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
|
|
253
|
+
{ event: "event2", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
result.forEach(event => {
|
|
257
|
+
expect(event.weight).toBeGreaterThanOrEqual(1);
|
|
258
|
+
expect(event.weight).toBeLessThanOrEqual(5);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('events: objects', () => {
|
|
263
|
+
const events = [{ event: "event1", properties: { a: 1 } }, { event: "event2", properties: { b: 2 } }];
|
|
264
|
+
const result = validateEventConfig(events);
|
|
265
|
+
|
|
266
|
+
expect(result).toEqual(events);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('events: mix', () => {
|
|
270
|
+
const events = ["event1", { event: "event2", properties: { b: 2 } }];
|
|
271
|
+
// @ts-ignore
|
|
272
|
+
const result = validateEventConfig(events);
|
|
273
|
+
|
|
274
|
+
expect(result).toEqual([
|
|
275
|
+
{ event: "event1", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
|
|
276
|
+
{ event: "event2", properties: { b: 2 } }
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
expect(result[0].weight).toBeGreaterThanOrEqual(1);
|
|
280
|
+
expect(result[0].weight).toBeLessThanOrEqual(5);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('dates: between', () => {
|
|
284
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 15); // 15 days ago
|
|
285
|
+
const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
286
|
+
const latestTime = global.NOW;
|
|
287
|
+
expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(true);
|
|
288
|
+
});
|
|
232
289
|
|
|
233
|
-
|
|
290
|
+
test('dates: outside earliest', () => {
|
|
291
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 31); // 31 days ago
|
|
292
|
+
const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
293
|
+
const latestTime = global.NOW;
|
|
294
|
+
expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(false);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test('dates: outside latest', () => {
|
|
298
|
+
const chosenTime = -1;
|
|
299
|
+
const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
|
|
300
|
+
const latestTime = global.NOW;
|
|
301
|
+
expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test('dates: inference in', () => {
|
|
305
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 15); // 15 days ago
|
|
306
|
+
expect(validateTime(chosenTime)).toBe(true);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test('dates: inference out', () => {
|
|
310
|
+
const chosenTime = global.NOW - (60 * 60 * 24 * 31); // 31 days ago
|
|
311
|
+
expect(validateTime(chosenTime)).toBe(false);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('enrichment', () => {
|
|
234
316
|
test('hook works', () => {
|
|
235
317
|
const arr = [];
|
|
236
318
|
const hook = (item) => item * 2;
|
|
@@ -238,7 +320,7 @@ describe('enrich array', () => {
|
|
|
238
320
|
enrichedArray.hookPush(1);
|
|
239
321
|
enrichedArray.hookPush(2);
|
|
240
322
|
expect(enrichedArray.includes(2)).toBeTruthy();
|
|
241
|
-
expect(enrichedArray.includes(4)).toBeTruthy();
|
|
323
|
+
expect(enrichedArray.includes(4)).toBeTruthy();
|
|
242
324
|
});
|
|
243
325
|
|
|
244
326
|
test('filter empties', () => {
|
|
@@ -247,7 +329,7 @@ describe('enrich array', () => {
|
|
|
247
329
|
const enrichedArray = enrichArray(arr, { hook });
|
|
248
330
|
enrichedArray.hookPush(null);
|
|
249
331
|
enrichedArray.hookPush(undefined);
|
|
250
|
-
enrichedArray.hookPush({});
|
|
332
|
+
enrichedArray.hookPush({});
|
|
251
333
|
enrichedArray.hookPush({ a: 1 });
|
|
252
334
|
enrichedArray.hookPush([1, 2]);
|
|
253
335
|
expect(enrichedArray).toHaveLength(3);
|
|
@@ -256,12 +338,14 @@ describe('enrich array', () => {
|
|
|
256
338
|
expect(enrichedArray.includes('[object Object]')).toBeTruthy();
|
|
257
339
|
expect(enrichedArray.includes('1')).toBeTruthy();
|
|
258
340
|
expect(enrichedArray.includes('2')).toBeTruthy();
|
|
259
|
-
|
|
341
|
+
|
|
260
342
|
});
|
|
343
|
+
|
|
344
|
+
|
|
261
345
|
});
|
|
262
346
|
|
|
263
347
|
|
|
264
|
-
describe('
|
|
348
|
+
describe('utilities', () => {
|
|
265
349
|
|
|
266
350
|
test('pick: works', () => {
|
|
267
351
|
const array = [1, 2, 3];
|
|
@@ -291,7 +375,7 @@ describe('utils', () => {
|
|
|
291
375
|
|
|
292
376
|
|
|
293
377
|
test('person: fields', () => {
|
|
294
|
-
const generatedPerson = person();
|
|
378
|
+
const generatedPerson = person('myId');
|
|
295
379
|
expect(generatedPerson).toHaveProperty('name');
|
|
296
380
|
expect(generatedPerson).toHaveProperty('email');
|
|
297
381
|
expect(generatedPerson).toHaveProperty('avatar');
|
|
@@ -343,13 +427,13 @@ describe('utils', () => {
|
|
|
343
427
|
test('weightedRange: within range', () => {
|
|
344
428
|
const values = weightedRange(5, 15);
|
|
345
429
|
expect(values.every(v => v >= 5 && v <= 15)).toBe(true);
|
|
346
|
-
expect(values.length).toBe(
|
|
430
|
+
expect(values.length).toBe(50);
|
|
347
431
|
});
|
|
348
432
|
|
|
349
433
|
test('applySkew: skews', () => {
|
|
350
434
|
const value = boxMullerRandom();
|
|
351
435
|
const skewedValue = applySkew(value, .25);
|
|
352
|
-
expect(Math.abs(skewedValue)).
|
|
436
|
+
expect(Math.abs(skewedValue)).toBeLessThanOrEqual(Math.abs(value));
|
|
353
437
|
});
|
|
354
438
|
|
|
355
439
|
test('mapToRange: works', () => {
|
|
@@ -537,5 +621,17 @@ describe('utils', () => {
|
|
|
537
621
|
expect(stdDev).toBeCloseTo(1, 1);
|
|
538
622
|
});
|
|
539
623
|
|
|
624
|
+
test('optimized box normal distribution', () => {
|
|
625
|
+
const values = [];
|
|
626
|
+
for (let i = 0; i < 10000; i++) {
|
|
627
|
+
values.push(optimizedBoxMuller());
|
|
628
|
+
}
|
|
629
|
+
const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
|
|
630
|
+
const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
|
|
631
|
+
const stdDev = Math.sqrt(variance);
|
|
632
|
+
expect(mean).toBeLessThan(1);
|
|
633
|
+
expect(stdDev).toBeLessThan(1);
|
|
634
|
+
});
|
|
635
|
+
|
|
540
636
|
|
|
541
637
|
});
|
package/types.d.ts
CHANGED
|
@@ -12,10 +12,21 @@ declare namespace main {
|
|
|
12
12
|
epochStart?: number;
|
|
13
13
|
epochEnd?: number;
|
|
14
14
|
numEvents?: number;
|
|
15
|
-
numUsers?: number;
|
|
15
|
+
numUsers?: number;
|
|
16
|
+
|
|
17
|
+
//switches
|
|
18
|
+
isAnonymous?: boolean;
|
|
19
|
+
hasLocation?: boolean;
|
|
20
|
+
hasCampaigns?: boolean;
|
|
21
|
+
hasAdSpend?: boolean;
|
|
22
|
+
hasIOSDevices?: boolean;
|
|
23
|
+
hasAndroidDevices?: boolean;
|
|
24
|
+
hasDesktopDevices?: boolean;
|
|
25
|
+
hasBrowser?: boolean;
|
|
26
|
+
|
|
27
|
+
|
|
16
28
|
format?: "csv" | "json";
|
|
17
29
|
region?: "US" | "EU";
|
|
18
|
-
chance?: any;
|
|
19
30
|
events?: EventConfig[]; //can also be a array of strings
|
|
20
31
|
superProps?: Record<string, ValueValid>;
|
|
21
32
|
funnels?: Funnel[];
|
|
@@ -50,6 +61,8 @@ declare namespace main {
|
|
|
50
61
|
| "mirror"
|
|
51
62
|
| "funnel-pre"
|
|
52
63
|
| "funnel-post"
|
|
64
|
+
| "ad-spend"
|
|
65
|
+
| "churn"
|
|
53
66
|
| "";
|
|
54
67
|
export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
|
|
55
68
|
|
|
@@ -60,7 +73,7 @@ declare namespace main {
|
|
|
60
73
|
}
|
|
61
74
|
|
|
62
75
|
export interface EnrichedArray<T> extends Array<T> {
|
|
63
|
-
hookPush: (item: T) =>
|
|
76
|
+
hookPush: (item: T) => boolean;
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
export interface EventConfig {
|
|
@@ -85,13 +98,20 @@ declare namespace main {
|
|
|
85
98
|
sequence: string[];
|
|
86
99
|
weight?: number;
|
|
87
100
|
isFirstFunnel?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* If true, the funnel will require the user to repeat the sequence of events in order to convert
|
|
103
|
+
* If false, the user does not need to repeat the sequence of events in order to convert
|
|
104
|
+
* ^ when false, users who repeat the repetitive steps are more likely to convert
|
|
105
|
+
*/
|
|
106
|
+
requireRepeats?: boolean;
|
|
88
107
|
order?:
|
|
89
108
|
| "sequential"
|
|
90
109
|
| "first-fixed"
|
|
91
110
|
| "last-fixed"
|
|
92
111
|
| "random"
|
|
93
112
|
| "first-and-last-fixed"
|
|
94
|
-
| "middle-fixed"
|
|
113
|
+
| "middle-fixed"
|
|
114
|
+
| "interrupted";
|
|
95
115
|
conversionRate?: number;
|
|
96
116
|
timeToConvert?: number;
|
|
97
117
|
props?: Record<string, ValueValid>;
|