make-mp-data 3.0.1 → 3.0.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/dungeons/adspend.js +35 -1
- package/dungeons/anon.js +25 -1
- package/dungeons/{array-of-object-loopup.js → array-of-object-lookup.js} +28 -8
- package/dungeons/benchmark-heavy.js +2 -2
- package/dungeons/benchmark-light.js +2 -2
- package/dungeons/big.js +2 -2
- package/dungeons/business.js +59 -12
- package/dungeons/complex.js +34 -1
- package/dungeons/copilot.js +1 -1
- package/dungeons/{harness/harness-education.js → education.js} +29 -12
- package/dungeons/experiments.js +15 -2
- package/dungeons/{harness/harness-fintech.js → fintech.js} +8 -8
- package/dungeons/foobar.js +33 -1
- package/dungeons/{harness/harness-food.js → food.js} +7 -4
- package/dungeons/funnels.js +38 -1
- package/dungeons/gaming.js +25 -5
- package/dungeons/media.js +861 -271
- package/dungeons/mil.js +29 -2
- package/dungeons/mirror.js +33 -1
- package/dungeons/{kurby.js → retention-cadence.js} +1 -1
- package/dungeons/{harness/harness-gaming.js → rpg.js} +5 -5
- package/dungeons/sanity.js +31 -2
- package/dungeons/{harness/harness-sass.js → sass.js} +2 -2
- package/dungeons/scd.js +46 -1
- package/dungeons/simple.js +1 -1
- package/dungeons/{harness/harness-social.js → social.js} +2 -2
- package/dungeons/streaming.js +373 -0
- package/dungeons/strict-event-test.js +1 -1
- package/dungeons/student-teacher.js +18 -5
- package/dungeons/text-generation.js +38 -1
- package/dungeons/too-big-events.js +38 -1
- package/dungeons/{userAgent.js → user-agent.js} +21 -1
- package/entry.js +5 -4
- package/lib/utils/logger.js +0 -4
- package/package.json +1 -4
- package/dungeons/ai-chat-analytics-ed.js +0 -275
- package/dungeons/clinch-agi.js +0 -632
- package/dungeons/ecommerce-store.js +0 -0
- package/dungeons/harness/harness-media.js +0 -961
- package/dungeons/money2020-ed-also.js +0 -277
- package/dungeons/money2020-ed.js +0 -580
- package/dungeons/uday-schema.json +0 -220
- package/lib/templates/funnels-instructions.txt +0 -272
- package/lib/templates/hook-examples.json +0 -187
- package/lib/templates/hooks-instructions.txt +0 -721
- package/lib/templates/refine-instructions.txt +0 -485
- package/lib/templates/schema-instructions.txt +0 -285
- package/lib/utils/ai.js +0 -896
- package/lib/utils/mixpanel.js +0 -101
- package/lib/utils/project.js +0 -167
package/dungeons/mil.js
CHANGED
|
@@ -25,7 +25,7 @@ const channelIds = genIds(100);
|
|
|
25
25
|
|
|
26
26
|
/** @type {Config} */
|
|
27
27
|
const config = {
|
|
28
|
-
token:
|
|
28
|
+
token: "",
|
|
29
29
|
seed: `LFG!`, //,
|
|
30
30
|
numDays: days,
|
|
31
31
|
numEvents: num_users * 62,
|
|
@@ -48,7 +48,7 @@ const config = {
|
|
|
48
48
|
|
|
49
49
|
batchSize: 2_500_000,
|
|
50
50
|
concurrency: 10,
|
|
51
|
-
writeToDisk:
|
|
51
|
+
writeToDisk: false,
|
|
52
52
|
|
|
53
53
|
funnels: [],
|
|
54
54
|
|
|
@@ -290,6 +290,33 @@ const config = {
|
|
|
290
290
|
groupProps: {},
|
|
291
291
|
lookupTables: [],
|
|
292
292
|
hook: function (record, type, meta) {
|
|
293
|
+
// event hook: tag events by frequency tier and enrich booleans
|
|
294
|
+
if (type === "event") {
|
|
295
|
+
const highFreq = ["foo", "bar", "baz"];
|
|
296
|
+
const lowFreq = ["crumn", "yak"];
|
|
297
|
+
if (highFreq.includes(record.event)) {
|
|
298
|
+
record.frequency_tier = "high";
|
|
299
|
+
} else if (lowFreq.includes(record.event)) {
|
|
300
|
+
record.frequency_tier = "low";
|
|
301
|
+
} else {
|
|
302
|
+
record.frequency_tier = "medium";
|
|
303
|
+
}
|
|
304
|
+
// boolean super prop drives a derived field
|
|
305
|
+
if (record.boolean === true) {
|
|
306
|
+
record.flag_status = "active";
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// everything hook: users with very few events are marked as churned
|
|
311
|
+
if (type === "everything") {
|
|
312
|
+
if (record.length <= 3) {
|
|
313
|
+
for (const e of record) {
|
|
314
|
+
e.is_churned_user = true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return record;
|
|
318
|
+
}
|
|
319
|
+
|
|
293
320
|
return record;
|
|
294
321
|
}
|
|
295
322
|
};
|
package/dungeons/mirror.js
CHANGED
|
@@ -19,7 +19,7 @@ import { pickAWinner, weighNumRange, date, integer } from "../lib/utils/utils.js
|
|
|
19
19
|
|
|
20
20
|
/** @type {import('../types').Dungeon} */
|
|
21
21
|
const config = {
|
|
22
|
-
token:
|
|
22
|
+
token: "",
|
|
23
23
|
seed: "mirror me",
|
|
24
24
|
numDays: 30, //how many days worth of data
|
|
25
25
|
numEvents: 10000, //how many events
|
|
@@ -120,6 +120,38 @@ const config = {
|
|
|
120
120
|
groupProps: {},
|
|
121
121
|
lookupTables: [],
|
|
122
122
|
hook: function (record, type, meta) {
|
|
123
|
+
// --- user hook: segment users by spirit animal ---
|
|
124
|
+
if (type === "user") {
|
|
125
|
+
const aquatic = ["whale", "dolphin", "shark", "octopus", "squid", "jellyfish", "seahorse", "crab", "lobster", "shrimp"];
|
|
126
|
+
record.segment = aquatic.includes(record.spiritAnimal) ? "aquatic" : "terrestrial";
|
|
127
|
+
return record;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- event hook: color-based property boost ---
|
|
131
|
+
if (type === "event") {
|
|
132
|
+
// warm-colored events get a higher updateMe value
|
|
133
|
+
const warmColors = ["red", "orange", "yellow"];
|
|
134
|
+
if (warmColors.includes(record.color)) {
|
|
135
|
+
record.updateMe = (record.updateMe || 5) + 10;
|
|
136
|
+
}
|
|
137
|
+
return record;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// --- everything hook: inject a "milestone" event for users with 8+ events ---
|
|
141
|
+
if (type === "everything") {
|
|
142
|
+
if (record.length >= 8) {
|
|
143
|
+
const lastEvent = record[record.length - 1];
|
|
144
|
+
record.push({
|
|
145
|
+
event: "milestone_reached",
|
|
146
|
+
time: lastEvent.time,
|
|
147
|
+
user_id: lastEvent.user_id,
|
|
148
|
+
color: lastEvent.color,
|
|
149
|
+
milestone: record.length
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return record;
|
|
153
|
+
}
|
|
154
|
+
|
|
123
155
|
return record;
|
|
124
156
|
}
|
|
125
157
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import dayjs from "dayjs";
|
|
2
2
|
import utc from "dayjs/plugin/utc.js";
|
|
3
3
|
import "dotenv/config";
|
|
4
|
-
import * as u from "
|
|
4
|
+
import * as u from "../lib/utils/utils.js";
|
|
5
5
|
import * as v from "ak-tools";
|
|
6
6
|
|
|
7
7
|
const SEED = "harness-gaming";
|
|
@@ -110,7 +110,7 @@ const itemIds = v.range(1, 301).map(n => `item_${v.uid(7)}`);
|
|
|
110
110
|
|
|
111
111
|
/** @type {Config} */
|
|
112
112
|
const config = {
|
|
113
|
-
token: "
|
|
113
|
+
token: "",
|
|
114
114
|
seed: SEED,
|
|
115
115
|
numDays: days,
|
|
116
116
|
numEvents: num_users * 120,
|
|
@@ -585,8 +585,8 @@ const config = {
|
|
|
585
585
|
const eventTime = dayjs(event.time);
|
|
586
586
|
const daysSinceStart = firstEventTime ? eventTime.diff(firstEventTime, 'days', true) : 0;
|
|
587
587
|
|
|
588
|
-
// Hook #8: Capture subscription tier from
|
|
589
|
-
if (
|
|
588
|
+
// Hook #8: Capture subscription tier from any event that has it
|
|
589
|
+
if (event.subscription_tier) {
|
|
590
590
|
subscriptionTier = event.subscription_tier;
|
|
591
591
|
}
|
|
592
592
|
|
|
@@ -1133,7 +1133,7 @@ export default config;
|
|
|
1133
1133
|
* import config from './dungeons/harness-gaming.js';
|
|
1134
1134
|
* const results = await generate(config);
|
|
1135
1135
|
*
|
|
1136
|
-
* OUTPUT FILES (with writeToDisk:
|
|
1136
|
+
* OUTPUT FILES (with writeToDisk: false, format: "parquet", gzip: true):
|
|
1137
1137
|
*
|
|
1138
1138
|
* - needle-in-haystack__events.parquet.gz - All event data
|
|
1139
1139
|
* - needle-in-haystack__user_profiles.parquet.gz - User profiles
|
package/dungeons/sanity.js
CHANGED
|
@@ -18,7 +18,7 @@ import { weighNumRange, integer } from "../lib/utils/utils.js";
|
|
|
18
18
|
|
|
19
19
|
/** @type {import('../types.js').Dungeon} */
|
|
20
20
|
const config = {
|
|
21
|
-
token:
|
|
21
|
+
token: "",
|
|
22
22
|
seed: "foo bar",
|
|
23
23
|
numDays: 90, //how many days worth of data
|
|
24
24
|
numEvents: 50_000, //how many events
|
|
@@ -29,7 +29,7 @@ const config = {
|
|
|
29
29
|
hasSessionIds: false, //if true, hasSessionIds are created for each user
|
|
30
30
|
alsoInferFunnels: true, //if true, infer funnels from events
|
|
31
31
|
makeChart: true,
|
|
32
|
-
writeToDisk:
|
|
32
|
+
writeToDisk: false,
|
|
33
33
|
concurrency: 25,
|
|
34
34
|
funnels: [
|
|
35
35
|
{
|
|
@@ -104,6 +104,35 @@ const config = {
|
|
|
104
104
|
spiritAnimal: ["duck", "dog", "otter", "penguin", "cat", "elephant", "lion", "cheetah", "giraffe", "zebra", "rhino", "hippo", "whale", "dolphin", "shark", "octopus", "squid", "jellyfish", "starfish", "seahorse", "crab", "lobster", "shrimp", "clam", "snail", "slug", "butterfly", "moth", "bee", "wasp", "ant", "beetle", "ladybug", "caterpillar", "centipede", "millipede", "scorpion", "spider", "tarantula", "tick", "mite", "mosquito", "fly", "dragonfly", "damselfly", "grasshopper", "cricket", "locust", "mantis", "cockroach", "termite", "praying mantis", "walking stick", "stick bug", "leaf insect", "lacewing", "aphid", "cicada", "thrips", "psyllid", "scale insect", "whitefly", "mealybug", "planthopper", "leafhopper", "treehopper", "flea", "louse", "bedbug", "flea beetle", "weevil", "longhorn beetle", "leaf beetle", "tiger beetle", "ground beetle", "lady beetle", "firefly", "click beetle", "rove beetle", "scarab beetle", "dung beetle", "stag beetle", "rhinoceros beetle", "hercules beetle", "goliath beetle", "jewel beetle", "tortoise beetle"]
|
|
105
105
|
},
|
|
106
106
|
hook: function (record, type, meta) {
|
|
107
|
+
// --- user hook: tag power users based on luckyNumber ---
|
|
108
|
+
if (type === "user") {
|
|
109
|
+
record.userTier = record.luckyNumber > 300 ? "power" : "regular";
|
|
110
|
+
return record;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// --- event hook: add an engagement score based on event type ---
|
|
114
|
+
if (type === "event") {
|
|
115
|
+
const highEngagement = ["fonk", "crumn", "yak"];
|
|
116
|
+
const midEngagement = ["garply", "durtle", "linny"];
|
|
117
|
+
if (highEngagement.includes(record.event)) {
|
|
118
|
+
record.engagement_score = chance.integer({ min: 70, max: 100 });
|
|
119
|
+
} else if (midEngagement.includes(record.event)) {
|
|
120
|
+
record.engagement_score = chance.integer({ min: 30, max: 69 });
|
|
121
|
+
} else {
|
|
122
|
+
record.engagement_score = chance.integer({ min: 1, max: 29 });
|
|
123
|
+
}
|
|
124
|
+
return record;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- everything hook: low-activity users lose their last few events (churn simulation) ---
|
|
128
|
+
if (type === "everything") {
|
|
129
|
+
if (record.length > 3 && record.length < 8) {
|
|
130
|
+
// users with few events lose the tail end — simulates churn
|
|
131
|
+
return record.slice(0, Math.ceil(record.length * 0.6));
|
|
132
|
+
}
|
|
133
|
+
return record;
|
|
134
|
+
}
|
|
135
|
+
|
|
107
136
|
return record;
|
|
108
137
|
}
|
|
109
138
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import dayjs from "dayjs";
|
|
2
2
|
import utc from "dayjs/plugin/utc.js";
|
|
3
3
|
import "dotenv/config";
|
|
4
|
-
import * as u from "
|
|
4
|
+
import * as u from "../lib/utils/utils.js";
|
|
5
5
|
import * as v from "ak-tools";
|
|
6
6
|
|
|
7
7
|
const SEED = "harness-sass";
|
|
@@ -78,7 +78,7 @@ const failedDeployUsers = new Map();
|
|
|
78
78
|
|
|
79
79
|
/** @type {Config} */
|
|
80
80
|
const config = {
|
|
81
|
-
token: "
|
|
81
|
+
token: "",
|
|
82
82
|
seed: SEED,
|
|
83
83
|
numDays: days,
|
|
84
84
|
numEvents: num_users * 120,
|
package/dungeons/scd.js
CHANGED
|
@@ -22,7 +22,7 @@ const videoCategories = ["funny", "educational", "inspirational", "music", "news
|
|
|
22
22
|
|
|
23
23
|
/** @type {import('../types').Dungeon} */
|
|
24
24
|
const config = {
|
|
25
|
-
token:
|
|
25
|
+
token: "",
|
|
26
26
|
seed: "simple is best",
|
|
27
27
|
numDays: 30, //how many days worth1 of data
|
|
28
28
|
numEvents: 50000, //how many events
|
|
@@ -196,6 +196,51 @@ const config = {
|
|
|
196
196
|
}
|
|
197
197
|
},
|
|
198
198
|
hook: function (record, type, meta) {
|
|
199
|
+
// --- user hook: classify users into spending tiers ---
|
|
200
|
+
if (type === "user") {
|
|
201
|
+
record.spendTier = record.luckyNumber > 250 ? "high_spender" : "budget";
|
|
202
|
+
return record;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// --- event hook: coupon users get discounted checkout amounts ---
|
|
206
|
+
if (type === "event") {
|
|
207
|
+
if (record.event === "checkout" && record.coupon && record.coupon !== "none") {
|
|
208
|
+
const discountPct = parseInt(record.coupon) || 10;
|
|
209
|
+
record.amount = Math.round(record.amount * (1 - discountPct / 100));
|
|
210
|
+
record.discount_applied = true;
|
|
211
|
+
}
|
|
212
|
+
// weekend watchers get longer watch times
|
|
213
|
+
if (record.event === "watch video" && record.time) {
|
|
214
|
+
const day = dayjs(record.time).day();
|
|
215
|
+
if (day === 0 || day === 6) {
|
|
216
|
+
record.watchTimeSec = Math.round((record.watchTimeSec || 60) * 1.5);
|
|
217
|
+
record.is_weekend = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return record;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// --- everything hook: simulate cart abandonment ---
|
|
224
|
+
if (type === "everything") {
|
|
225
|
+
const hasAddToCart = record.some(e => e.event === "add to cart");
|
|
226
|
+
const hasCheckout = record.some(e => e.event === "checkout");
|
|
227
|
+
// users who added to cart but never checked out: remove checkout events (if any slipped through)
|
|
228
|
+
// and mark them as abandoned
|
|
229
|
+
if (hasAddToCart && !hasCheckout && record.length > 2) {
|
|
230
|
+
const lastAdd = record.filter(e => e.event === "add to cart").pop();
|
|
231
|
+
if (lastAdd) {
|
|
232
|
+
record.push({
|
|
233
|
+
event: "cart_abandoned",
|
|
234
|
+
time: dayjs(lastAdd.time).add(30, "minute").toISOString(),
|
|
235
|
+
user_id: lastAdd.user_id,
|
|
236
|
+
platform: lastAdd.platform,
|
|
237
|
+
amount: lastAdd.amount
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return record;
|
|
242
|
+
}
|
|
243
|
+
|
|
199
244
|
return record;
|
|
200
245
|
}
|
|
201
246
|
};
|
package/dungeons/simple.js
CHANGED
|
@@ -22,7 +22,7 @@ const videoCategories = ["funny", "educational", "inspirational", "music", "news
|
|
|
22
22
|
|
|
23
23
|
/** @type {import('../types').Dungeon} */
|
|
24
24
|
const config = {
|
|
25
|
-
token:
|
|
25
|
+
token: "",
|
|
26
26
|
seed: "simple is best",
|
|
27
27
|
numDays: 100, //how many days worth1 of data
|
|
28
28
|
numEvents: 1_000_000, //how many events
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import dayjs from "dayjs";
|
|
2
2
|
import utc from "dayjs/plugin/utc.js";
|
|
3
3
|
import "dotenv/config";
|
|
4
|
-
import * as u from "
|
|
4
|
+
import * as u from "../lib/utils/utils.js";
|
|
5
5
|
import * as v from "ak-tools";
|
|
6
6
|
|
|
7
7
|
const SEED = "harness-social";
|
|
@@ -98,7 +98,7 @@ const postIds = v.range(1, 1001).map(n => `post_${v.uid(8)}`);
|
|
|
98
98
|
|
|
99
99
|
/** @type {Config} */
|
|
100
100
|
const config = {
|
|
101
|
-
token: "
|
|
101
|
+
token: "",
|
|
102
102
|
seed: SEED,
|
|
103
103
|
numDays: days,
|
|
104
104
|
numEvents: num_users * 120,
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
|
|
2
|
+
const SEED = "my-seed";
|
|
3
|
+
import dayjs from 'dayjs';
|
|
4
|
+
import utc from 'dayjs/plugin/utc.js';
|
|
5
|
+
dayjs.extend(utc);
|
|
6
|
+
import 'dotenv/config';
|
|
7
|
+
import * as u from "../lib/utils/utils.js";
|
|
8
|
+
import * as v from 'ak-tools';
|
|
9
|
+
const chance = u.initChance(SEED);
|
|
10
|
+
const num_users = 10_000;
|
|
11
|
+
const days = 125;
|
|
12
|
+
|
|
13
|
+
/** @typedef {import("../types.js").Dungeon} Config */
|
|
14
|
+
|
|
15
|
+
function genIds(numIds = 1000) {
|
|
16
|
+
const ids = [];
|
|
17
|
+
for (let i = 0; i < numIds; i++) {
|
|
18
|
+
ids.push(v.uid());
|
|
19
|
+
}
|
|
20
|
+
return ids;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const videoIds = genIds();
|
|
24
|
+
const channelIds = genIds(100);
|
|
25
|
+
|
|
26
|
+
/** @type {Config} */
|
|
27
|
+
const config = {
|
|
28
|
+
// token: "",
|
|
29
|
+
seed: `LFG!`, //,
|
|
30
|
+
numDays: days,
|
|
31
|
+
numEvents: num_users * 63,
|
|
32
|
+
numUsers: num_users,
|
|
33
|
+
hasAnonIds: false,
|
|
34
|
+
hasSessionIds: false,
|
|
35
|
+
format: "csv",
|
|
36
|
+
alsoInferFunnels: false,
|
|
37
|
+
hasLocation: false,
|
|
38
|
+
hasAndroidDevices: true,
|
|
39
|
+
hasIOSDevices: true,
|
|
40
|
+
hasDesktopDevices: true,
|
|
41
|
+
hasBrowser: false,
|
|
42
|
+
hasCampaigns: false,
|
|
43
|
+
isAnonymous: false,
|
|
44
|
+
hasAdSpend: false,
|
|
45
|
+
|
|
46
|
+
hasAvatar: false,
|
|
47
|
+
makeChart: false,
|
|
48
|
+
|
|
49
|
+
batchSize: 2_500_000,
|
|
50
|
+
concurrency: 10,
|
|
51
|
+
writeToDisk: false,
|
|
52
|
+
|
|
53
|
+
funnels: [],
|
|
54
|
+
|
|
55
|
+
events: [
|
|
56
|
+
{
|
|
57
|
+
event: "watch video",
|
|
58
|
+
weight: 55,
|
|
59
|
+
properties: {
|
|
60
|
+
video_id: u.pickAWinner(videoIds),
|
|
61
|
+
"watch percent": u.pickAWinner([
|
|
62
|
+
25,
|
|
63
|
+
50,
|
|
64
|
+
75,
|
|
65
|
+
100,
|
|
66
|
+
]),
|
|
67
|
+
"watch time": u.weighNumRange(1, 65, .89, 100),
|
|
68
|
+
|
|
69
|
+
"category": u.pickAWinner([
|
|
70
|
+
"comedy",
|
|
71
|
+
"educational",
|
|
72
|
+
"music",
|
|
73
|
+
"sports",
|
|
74
|
+
"news",
|
|
75
|
+
"gaming",
|
|
76
|
+
"travel",
|
|
77
|
+
]),
|
|
78
|
+
quality: u.pickAWinner([
|
|
79
|
+
"240p",
|
|
80
|
+
"360p",
|
|
81
|
+
"480p",
|
|
82
|
+
"720p",
|
|
83
|
+
"1080p",
|
|
84
|
+
"4k",
|
|
85
|
+
], 4),
|
|
86
|
+
autoplay: [
|
|
87
|
+
true,
|
|
88
|
+
false,
|
|
89
|
+
],
|
|
90
|
+
fullscreen: [
|
|
91
|
+
true,
|
|
92
|
+
false,
|
|
93
|
+
],
|
|
94
|
+
"ads?": [
|
|
95
|
+
true, true,
|
|
96
|
+
false,
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
event: "like",
|
|
102
|
+
weight: 10,
|
|
103
|
+
properties: {
|
|
104
|
+
video_id: u.pickAWinner(videoIds),
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
event: "comment",
|
|
109
|
+
weight: 5,
|
|
110
|
+
properties: {
|
|
111
|
+
video_id: u.pickAWinner(videoIds),
|
|
112
|
+
comment_length: [
|
|
113
|
+
"short",
|
|
114
|
+
"medium",
|
|
115
|
+
"long",
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
event: "share",
|
|
121
|
+
weight: 3,
|
|
122
|
+
properties: {
|
|
123
|
+
video_id: u.pickAWinner(videoIds),
|
|
124
|
+
"share network": u.pickAWinner([
|
|
125
|
+
"facebook",
|
|
126
|
+
"twitter",
|
|
127
|
+
"reddit",
|
|
128
|
+
"email",
|
|
129
|
+
"whatsapp",
|
|
130
|
+
]),
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
event: "search",
|
|
135
|
+
weight: 25,
|
|
136
|
+
properties: {
|
|
137
|
+
search_term: [
|
|
138
|
+
"cats",
|
|
139
|
+
"dogs",
|
|
140
|
+
"tutorial",
|
|
141
|
+
"news",
|
|
142
|
+
"music",
|
|
143
|
+
],
|
|
144
|
+
"results count": u.pickAWinner([
|
|
145
|
+
0,
|
|
146
|
+
1,
|
|
147
|
+
2,
|
|
148
|
+
3,
|
|
149
|
+
4,
|
|
150
|
+
5,
|
|
151
|
+
6, 7, 8, 9, 10
|
|
152
|
+
], 5),
|
|
153
|
+
"search category": [
|
|
154
|
+
"all",
|
|
155
|
+
"channels",
|
|
156
|
+
"playlists",
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
event: "subscribe",
|
|
162
|
+
weight: 7,
|
|
163
|
+
properties: {
|
|
164
|
+
channel_id: u.pickAWinner(genIds()),
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
event: "unsubscribe",
|
|
169
|
+
weight: 2,
|
|
170
|
+
properties: {
|
|
171
|
+
channel_id: u.pickAWinner(genIds()),
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
event: "create playlist",
|
|
176
|
+
weight: 4,
|
|
177
|
+
properties: {
|
|
178
|
+
"play list name": u.pickAWinner([
|
|
179
|
+
"favorites",
|
|
180
|
+
"watch later",
|
|
181
|
+
"my music",
|
|
182
|
+
"funny videos",
|
|
183
|
+
"educational",
|
|
184
|
+
]),
|
|
185
|
+
privacy: u.pickAWinner([
|
|
186
|
+
"public",
|
|
187
|
+
"private",
|
|
188
|
+
"unlisted",
|
|
189
|
+
]),
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
event: "account signup",
|
|
194
|
+
weight: 1,
|
|
195
|
+
isFirstEvent: true,
|
|
196
|
+
properties: {
|
|
197
|
+
"sign up method": [
|
|
198
|
+
"email",
|
|
199
|
+
"google",
|
|
200
|
+
"facebook",
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
event: "account login",
|
|
206
|
+
weight: 9,
|
|
207
|
+
properties: {
|
|
208
|
+
"log in method": u.pickAWinner([
|
|
209
|
+
"email",
|
|
210
|
+
"google",
|
|
211
|
+
"facebook",
|
|
212
|
+
]),
|
|
213
|
+
success: [
|
|
214
|
+
true,
|
|
215
|
+
false,
|
|
216
|
+
],
|
|
217
|
+
error_message: [
|
|
218
|
+
"incorrect password",
|
|
219
|
+
"user not found",
|
|
220
|
+
"account locked",
|
|
221
|
+
],
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
event: "$experiment_started",
|
|
226
|
+
weight: 5,
|
|
227
|
+
isSessionStartEvent: true,
|
|
228
|
+
properties: {
|
|
229
|
+
"$experiment_type": "ak_ad_hoc",
|
|
230
|
+
"Experiment name": "show results on empty search",
|
|
231
|
+
"Variant name": ["feature enabled", "feature disabled"],
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
superProps: {
|
|
236
|
+
platform: u.pickAWinner([
|
|
237
|
+
"web",
|
|
238
|
+
"ios",
|
|
239
|
+
"android",
|
|
240
|
+
]),
|
|
241
|
+
network_type: [
|
|
242
|
+
"wifi",
|
|
243
|
+
"cellular",
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
userProps: {
|
|
247
|
+
subscription_status: [
|
|
248
|
+
"free",
|
|
249
|
+
"free",
|
|
250
|
+
"premium",
|
|
251
|
+
],
|
|
252
|
+
age_range: [
|
|
253
|
+
"13-17",
|
|
254
|
+
"18-24",
|
|
255
|
+
"25-34",
|
|
256
|
+
"35-44",
|
|
257
|
+
"45-54",
|
|
258
|
+
"55+",
|
|
259
|
+
],
|
|
260
|
+
preferred_genre: u.pickAWinner([
|
|
261
|
+
"comedy",
|
|
262
|
+
"action",
|
|
263
|
+
"drama",
|
|
264
|
+
"sci-fi",
|
|
265
|
+
"horror",
|
|
266
|
+
]),
|
|
267
|
+
upload_count: [
|
|
268
|
+
0,
|
|
269
|
+
1,
|
|
270
|
+
5,
|
|
271
|
+
10,
|
|
272
|
+
20,
|
|
273
|
+
],
|
|
274
|
+
following_count: [
|
|
275
|
+
0,
|
|
276
|
+
10,
|
|
277
|
+
50,
|
|
278
|
+
100,
|
|
279
|
+
500,
|
|
280
|
+
],
|
|
281
|
+
dark_mode_enabled: [
|
|
282
|
+
true,
|
|
283
|
+
false,
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
scdProps: {},
|
|
288
|
+
mirrorProps: {},
|
|
289
|
+
groupKeys: [],
|
|
290
|
+
groupProps: {},
|
|
291
|
+
lookupTables: [],
|
|
292
|
+
hook: function (record, type, meta) {
|
|
293
|
+
const NOW = dayjs();
|
|
294
|
+
const TIME_WHEN_SEARCH_GOT_BAD = NOW.subtract(21, 'days');
|
|
295
|
+
const TIME_WE_EXPERIMENTED = NOW.subtract(14, 'days');
|
|
296
|
+
|
|
297
|
+
if (type === "event") {
|
|
298
|
+
const EVENT_TIME = dayjs(record.time);
|
|
299
|
+
//when search got bad, people started searching less
|
|
300
|
+
//and got fewer results
|
|
301
|
+
if (EVENT_TIME.isAfter(TIME_WHEN_SEARCH_GOT_BAD)) {
|
|
302
|
+
if (chance.bool({ likelihood: 50 })) {
|
|
303
|
+
if (record.event === "search") {
|
|
304
|
+
record["results count"] = 0;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (chance.bool({ likelihood: 18 })) {
|
|
309
|
+
record._drop = true;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (EVENT_TIME.isBefore(TIME_WE_EXPERIMENTED)) {
|
|
314
|
+
if (record.event === "$experiment_started") {
|
|
315
|
+
record._drop = true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
if (type === "everything") {
|
|
323
|
+
// Filter out events tagged for dropping by the event hook
|
|
324
|
+
record = record.filter(e => !e._drop);
|
|
325
|
+
|
|
326
|
+
const hadFeatureEnabled = record.some(event =>
|
|
327
|
+
event.event === "$experiment_started" &&
|
|
328
|
+
event["Variant name"] === "feature enabled"
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
const hadFeatureDisabled = record.some(event =>
|
|
332
|
+
event.event === "$experiment_started" &&
|
|
333
|
+
event["Variant name"] === "feature disabled"
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
record.forEach((event, idx) => {
|
|
337
|
+
const EVENT_TIME = dayjs(event.time);
|
|
338
|
+
|
|
339
|
+
if (EVENT_TIME.isAfter(TIME_WE_EXPERIMENTED)) {
|
|
340
|
+
if (hadFeatureEnabled) {
|
|
341
|
+
// Users with feature enabled variant have a higher likelihood of subscribing.
|
|
342
|
+
// Add an extra subscribe event 50% of the time immediately after watching a video.
|
|
343
|
+
if (event.event === "watch video" && chance.bool({ likelihood: 75 })) {
|
|
344
|
+
// watch time goes up
|
|
345
|
+
event["watch time"] = v.round(event["watch time"] * 1.7);
|
|
346
|
+
const subscribeEvent = {
|
|
347
|
+
event: "subscribe",
|
|
348
|
+
time: dayjs(event.time).add(1, 'minute').toISOString(),
|
|
349
|
+
user_id: event.user_id,
|
|
350
|
+
};
|
|
351
|
+
record.splice(idx + 1, 0, subscribeEvent);
|
|
352
|
+
}
|
|
353
|
+
} else if (hadFeatureDisabled) {
|
|
354
|
+
// Users with feature disabled variant have lower likelihood of subscribing.
|
|
355
|
+
// Drop subscribe events 50% of the time.
|
|
356
|
+
if (event.event === "subscribe" && chance.bool({ likelihood: 75 })) {
|
|
357
|
+
record.splice(idx, 1);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// watch time goes down
|
|
361
|
+
if (event.event === "watch video") {
|
|
362
|
+
event["watch time"] = v.round(event["watch time"] * 0.5);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return record;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
export default config;
|