make-mp-data 2.0.1 → 2.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 +96 -0
- package/dungeons/anon.js +104 -0
- package/dungeons/big.js +225 -0
- package/dungeons/business.js +345 -0
- package/dungeons/complex.js +396 -0
- package/dungeons/experiments.js +125 -0
- package/dungeons/foobar.js +241 -0
- package/dungeons/funnels.js +272 -0
- package/dungeons/gaming.js +315 -0
- package/dungeons/media.js +7 -7
- package/dungeons/mirror.js +129 -0
- package/dungeons/sanity.js +113 -0
- package/dungeons/scd.js +205 -0
- package/dungeons/simple.js +195 -0
- package/dungeons/userAgent.js +190 -0
- package/entry.js +57 -0
- package/index.js +96 -68
- package/lib/cli/cli.js +5 -4
- package/lib/core/config-validator.js +28 -12
- package/lib/core/context.js +147 -130
- package/lib/core/storage.js +45 -31
- package/lib/data/defaults.js +2 -2
- package/lib/generators/adspend.js +1 -2
- package/lib/generators/events.js +35 -24
- package/lib/generators/funnels.js +1 -2
- package/lib/generators/mirror.js +1 -2
- package/lib/orchestrators/mixpanel-sender.js +15 -7
- package/lib/orchestrators/user-loop.js +21 -10
- package/lib/orchestrators/worker-manager.js +5 -2
- package/lib/utils/ai.js +36 -63
- package/lib/utils/chart.js +5 -0
- package/lib/utils/instructions.txt +593 -0
- package/lib/utils/utils.js +162 -38
- package/package.json +23 -9
- package/types.d.ts +376 -376
- package/.claude/settings.local.json +0 -21
- package/.gcloudignore +0 -18
- package/.gitattributes +0 -2
- package/.prettierrc +0 -0
- package/.vscode/launch.json +0 -80
- package/.vscode/settings.json +0 -69
- package/.vscode/tasks.json +0 -12
- package/dungeons/customers/.gitkeep +0 -0
- package/env.yaml +0 -1
- package/lib/cloud-function.js +0 -20
- package/scratch.mjs +0 -62
- package/scripts/dana.mjs +0 -137
- package/scripts/deploy.sh +0 -15
- package/scripts/jsdoctest.js +0 -5
- package/scripts/new-dungeon.sh +0 -98
- package/scripts/new-project.mjs +0 -14
- package/scripts/run-index.sh +0 -2
- package/scripts/update-deps.sh +0 -5
- package/tests/benchmark/concurrency.mjs +0 -52
- package/tests/cli.test.js +0 -124
- package/tests/coverage/.gitkeep +0 -0
- package/tests/e2e.test.js +0 -379
- package/tests/int.test.js +0 -715
- package/tests/testCases.mjs +0 -229
- package/tests/testSoup.mjs +0 -28
- package/tests/unit.test.js +0 -910
- package/tmp/.gitkeep +0 -0
- package/tsconfig.json +0 -18
- package/vitest.config.js +0 -47
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
you are an AI assistant generating a spec for an analytics data set
|
|
2
|
+
|
|
3
|
+
the spec you are building is a javascript object... the high level spec looks like this:
|
|
4
|
+
|
|
5
|
+
--------------
|
|
6
|
+
|
|
7
|
+
const my_dungeon = {
|
|
8
|
+
funnels: [],
|
|
9
|
+
events: [],
|
|
10
|
+
superProps: {},
|
|
11
|
+
userProps: {},
|
|
12
|
+
scdProps: {},
|
|
13
|
+
mirrorProps: {},
|
|
14
|
+
groupKeys: [],
|
|
15
|
+
groupProps: {},
|
|
16
|
+
lookupTables: [],
|
|
17
|
+
hook: function (record, type, meta) {
|
|
18
|
+
if (type === "everything") {
|
|
19
|
+
// inside here we have access to the each user's complete event stream which lets us inject trends into the data
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return record;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
--------------
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
here's the full typescript definition for each of the properties in the entire spec; we call the specs 'DUNGEONS' and each dungeon is a javascript object:
|
|
30
|
+
|
|
31
|
+
--------------
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* main config object for the entire data generation
|
|
35
|
+
*/
|
|
36
|
+
export interface Dungeon {
|
|
37
|
+
// constants
|
|
38
|
+
token?: string;
|
|
39
|
+
seed?: string;
|
|
40
|
+
numDays?: number;
|
|
41
|
+
epochStart?: number;
|
|
42
|
+
epochEnd?: number;
|
|
43
|
+
numEvents?: number;
|
|
44
|
+
numUsers?: number;
|
|
45
|
+
format?: "csv" | "json" | string;
|
|
46
|
+
region?: "US" | "EU";
|
|
47
|
+
concurrency?: number;
|
|
48
|
+
batchSize?: number;
|
|
49
|
+
|
|
50
|
+
serviceAccount?: string;
|
|
51
|
+
serviceSecret?: string;
|
|
52
|
+
projectId?: string;
|
|
53
|
+
|
|
54
|
+
// ids
|
|
55
|
+
simulationName?: string;
|
|
56
|
+
name?: string;
|
|
57
|
+
|
|
58
|
+
//switches
|
|
59
|
+
isAnonymous?: boolean;
|
|
60
|
+
hasAvatar?: boolean;
|
|
61
|
+
hasLocation?: boolean;
|
|
62
|
+
hasCampaigns?: boolean;
|
|
63
|
+
hasAdSpend?: boolean;
|
|
64
|
+
hasIOSDevices?: boolean;
|
|
65
|
+
hasAndroidDevices?: boolean;
|
|
66
|
+
hasDesktopDevices?: boolean;
|
|
67
|
+
hasBrowser?: boolean;
|
|
68
|
+
writeToDisk?: boolean | string;
|
|
69
|
+
verbose?: boolean;
|
|
70
|
+
hasAnonIds?: boolean;
|
|
71
|
+
hasSessionIds?: boolean;
|
|
72
|
+
alsoInferFunnels?: boolean;
|
|
73
|
+
makeChart?: boolean | string;
|
|
74
|
+
singleCountry?: string;
|
|
75
|
+
|
|
76
|
+
//models
|
|
77
|
+
events?: EventConfig[]; //| string[]; //can also be a array of strings
|
|
78
|
+
superProps?: Record<string, ValueValid>;
|
|
79
|
+
funnels?: Funnel[];
|
|
80
|
+
userProps?: Record<string, ValueValid>;
|
|
81
|
+
scdProps?: Record<string, SCDProp>;
|
|
82
|
+
mirrorProps?: Record<string, MirrorProps>;
|
|
83
|
+
groupKeys?: [string, number][] | [string, number, string[]][]; // [key, numGroups, [events]]
|
|
84
|
+
groupProps?: Record<string, Record<string, ValueValid>>;
|
|
85
|
+
groupEvents?: GroupEventConfig[];
|
|
86
|
+
lookupTables?: LookupTableSchema[];
|
|
87
|
+
soup?: soup;
|
|
88
|
+
hook?: Hook<any>;
|
|
89
|
+
|
|
90
|
+
//allow anything to be on the config
|
|
91
|
+
[key: string]: any;
|
|
92
|
+
|
|
93
|
+
//probabilities
|
|
94
|
+
percentUsersBornInDataset?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type SCDProp = {
|
|
98
|
+
type?: string | "user" | "company_id" | "team_id" | "department_id";
|
|
99
|
+
frequency: "day" | "week" | "month" | "year";
|
|
100
|
+
values: ValueValid;
|
|
101
|
+
timing: "fixed" | "fuzzy";
|
|
102
|
+
max?: number;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* the soup is a set of parameters that determine the distribution of events over time
|
|
107
|
+
*/
|
|
108
|
+
type soup = {
|
|
109
|
+
deviation?: number;
|
|
110
|
+
peaks?: number;
|
|
111
|
+
mean?: number;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* the types of hooks that can be used
|
|
116
|
+
*/
|
|
117
|
+
type hookTypes =
|
|
118
|
+
| "event"
|
|
119
|
+
| "user"
|
|
120
|
+
| "group"
|
|
121
|
+
| "lookup"
|
|
122
|
+
| "scd"
|
|
123
|
+
| "scd-pre"
|
|
124
|
+
| "mirror"
|
|
125
|
+
| "funnel-pre"
|
|
126
|
+
| "funnel-post"
|
|
127
|
+
| "ad-spend"
|
|
128
|
+
| "churn"
|
|
129
|
+
| "group-event"
|
|
130
|
+
| "everything"
|
|
131
|
+
| "";
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* a hook is a function that can be called before each entity is created, and can be used to modify attributes
|
|
135
|
+
*/
|
|
136
|
+
export type Hook<T> = (record: any, type: hookTypes, meta: any) => T;
|
|
137
|
+
|
|
138
|
+
export interface hookArrayOptions<T> {
|
|
139
|
+
hook?: Hook<T>;
|
|
140
|
+
type?: hookTypes;
|
|
141
|
+
filename?: string;
|
|
142
|
+
format?: "csv" | "json" | string;
|
|
143
|
+
concurrency?: number;
|
|
144
|
+
[key: string]: any;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* an enriched array is an array that has a hookPush method that can be used to transform-then-push items into the array
|
|
149
|
+
*/
|
|
150
|
+
export interface HookedArray<T> extends Array<T> {
|
|
151
|
+
hookPush: (item: T | T[], ...meta) => any;
|
|
152
|
+
flush: () => void;
|
|
153
|
+
getWriteDir: () => string;
|
|
154
|
+
getWritePath: () => string;
|
|
155
|
+
[key: string]: any;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export type AllData =
|
|
159
|
+
| HookedArray<EventSchema>
|
|
160
|
+
| HookedArray<UserProfile>
|
|
161
|
+
| HookedArray<GroupProfileSchema>
|
|
162
|
+
| HookedArray<LookupTableSchema>
|
|
163
|
+
| HookedArray<SCDSchema>
|
|
164
|
+
| any[];
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* the storage object is a key-value store that holds arrays of data
|
|
168
|
+
*/
|
|
169
|
+
export interface Storage {
|
|
170
|
+
eventData?: HookedArray<EventSchema>;
|
|
171
|
+
mirrorEventData?: HookedArray<EventSchema>;
|
|
172
|
+
userProfilesData?: HookedArray<UserProfile>;
|
|
173
|
+
adSpendData?: HookedArray<EventSchema>;
|
|
174
|
+
groupProfilesData?: HookedArray<GroupProfileSchema>[];
|
|
175
|
+
lookupTableData?: HookedArray<LookupTableSchema>[];
|
|
176
|
+
scdTableData?: HookedArray<SCDSchema>[];
|
|
177
|
+
groupEventData?: HookedArray<EventSchema>;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Runtime state for tracking execution metrics and flags
|
|
182
|
+
*/
|
|
183
|
+
export interface RuntimeState {
|
|
184
|
+
operations: number;
|
|
185
|
+
eventCount: number;
|
|
186
|
+
userCount: number;
|
|
187
|
+
isBatchMode: boolean;
|
|
188
|
+
verbose: boolean;
|
|
189
|
+
isCLI: boolean;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Default data factories for generating realistic test data
|
|
194
|
+
*/
|
|
195
|
+
export interface Defaults {
|
|
196
|
+
locationsUsers: () => any[];
|
|
197
|
+
locationsEvents: () => any[];
|
|
198
|
+
iOSDevices: () => any[];
|
|
199
|
+
androidDevices: () => any[];
|
|
200
|
+
desktopDevices: () => any[];
|
|
201
|
+
browsers: () => any[];
|
|
202
|
+
campaigns: () => any[];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Context object that replaces global variables with dependency injection
|
|
207
|
+
* Contains validated config, storage containers, defaults, and runtime state
|
|
208
|
+
*/
|
|
209
|
+
export interface Context {
|
|
210
|
+
config: Dungeon;
|
|
211
|
+
storage: Storage | null;
|
|
212
|
+
defaults: Defaults;
|
|
213
|
+
campaigns: any[];
|
|
214
|
+
runtime: RuntimeState;
|
|
215
|
+
FIXED_NOW: number;
|
|
216
|
+
FIXED_BEGIN?: number;
|
|
217
|
+
|
|
218
|
+
// State update methods
|
|
219
|
+
incrementOperations(): void;
|
|
220
|
+
incrementEvents(): void;
|
|
221
|
+
incrementUsers(): void;
|
|
222
|
+
setStorage(storage: Storage): void;
|
|
223
|
+
|
|
224
|
+
// State getter methods
|
|
225
|
+
getOperations(): number;
|
|
226
|
+
getEventCount(): number;
|
|
227
|
+
getUserCount(): number;
|
|
228
|
+
incrementUserCount(): void;
|
|
229
|
+
incrementEventCount(): void;
|
|
230
|
+
isBatchMode(): boolean;
|
|
231
|
+
isCLI(): boolean;
|
|
232
|
+
|
|
233
|
+
// Time helper methods
|
|
234
|
+
getTimeShift(): number;
|
|
235
|
+
getDaysShift(): number;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* how we define events and their properties
|
|
240
|
+
*/
|
|
241
|
+
export interface EventConfig {
|
|
242
|
+
event?: string;
|
|
243
|
+
weight?: number;
|
|
244
|
+
properties?: Record<string, ValueValid>;
|
|
245
|
+
isFirstEvent?: boolean;
|
|
246
|
+
isChurnEvent?: boolean;
|
|
247
|
+
isSessionStartEvent?: boolean;
|
|
248
|
+
relativeTimeMs?: number;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export interface GroupEventConfig extends EventConfig {
|
|
252
|
+
frequency: number; //how often the event occurs (in days)
|
|
253
|
+
group_key: string; //the key that the group is based on
|
|
254
|
+
attribute_to_user: boolean; //if true, the event also goes to a user
|
|
255
|
+
group_size: number; //the number of users in the group
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* the generated event data
|
|
260
|
+
*/
|
|
261
|
+
export interface EventSchema {
|
|
262
|
+
event: string;
|
|
263
|
+
time: string;
|
|
264
|
+
source: string;
|
|
265
|
+
insert_id: string;
|
|
266
|
+
device_id?: string;
|
|
267
|
+
session_id?: string;
|
|
268
|
+
user_id?: string;
|
|
269
|
+
[key: string]: ValueValid;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* how we define funnels and their properties
|
|
274
|
+
*/
|
|
275
|
+
export interface Funnel {
|
|
276
|
+
/**
|
|
277
|
+
* the sequence of events that define the funnel
|
|
278
|
+
*/
|
|
279
|
+
sequence: string[];
|
|
280
|
+
/**
|
|
281
|
+
* how likely the funnel is to be selected
|
|
282
|
+
*/
|
|
283
|
+
weight?: number;
|
|
284
|
+
/**
|
|
285
|
+
* If true, the funnel will be the first thing the user does
|
|
286
|
+
*/
|
|
287
|
+
isFirstFunnel?: boolean;
|
|
288
|
+
/**
|
|
289
|
+
* If true, the funnel will require the user to repeat the sequence of events in order to convert
|
|
290
|
+
* If false, the user does not need to repeat the sequence of events in order to convert
|
|
291
|
+
* ^ when false, users who repeat the repetitive steps are more likely to convert
|
|
292
|
+
*/
|
|
293
|
+
requireRepeats?: boolean;
|
|
294
|
+
/**
|
|
295
|
+
* how the events in the funnel are ordered for each user
|
|
296
|
+
*/
|
|
297
|
+
order?:
|
|
298
|
+
| string
|
|
299
|
+
| "sequential"
|
|
300
|
+
| "first-fixed"
|
|
301
|
+
| "last-fixed"
|
|
302
|
+
| "random" //totally shuffled
|
|
303
|
+
| "first-and-last-fixed"
|
|
304
|
+
| "middle-fixed"
|
|
305
|
+
| "interrupted";
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* todo: implement this
|
|
309
|
+
* if set, the funnel might be the last thing the user does
|
|
310
|
+
* ^ the numerical value is the likelihood that the user will churn
|
|
311
|
+
* todo: allow for users to be resurrected
|
|
312
|
+
*/
|
|
313
|
+
isChurnFunnel?: void | number;
|
|
314
|
+
/**
|
|
315
|
+
* the likelihood that a user will convert (0-100%)
|
|
316
|
+
*/
|
|
317
|
+
conversionRate?: number;
|
|
318
|
+
/**
|
|
319
|
+
* the time it takes (on average) to convert in hours
|
|
320
|
+
*/
|
|
321
|
+
timeToConvert?: number;
|
|
322
|
+
/**
|
|
323
|
+
* funnel properties go onto each event in the funnel and are held constant
|
|
324
|
+
*/
|
|
325
|
+
props?: Record<string, ValueValid>;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* mirror props are used to show mutations of event data over time
|
|
330
|
+
* there are different strategies for how to mutate the data
|
|
331
|
+
*/
|
|
332
|
+
export interface MirrorProps {
|
|
333
|
+
/**
|
|
334
|
+
* the event that will be mutated in the new version
|
|
335
|
+
*/
|
|
336
|
+
events?: string[] | "*";
|
|
337
|
+
/**
|
|
338
|
+
* "create" - create this key in the new version; value are chosen
|
|
339
|
+
* "update" - update this key in the new version; values are chosen
|
|
340
|
+
* "fill" - update this key in the new version, but only if the existing key is null or unset
|
|
341
|
+
* "delete" - delete this key in the new version; values are ignored
|
|
342
|
+
*/
|
|
343
|
+
strategy?: "create" | "update" | "fill" | "delete" | "";
|
|
344
|
+
values?: ValueValid[];
|
|
345
|
+
/**
|
|
346
|
+
* optional: for 'fill' mode, daysUnfilled will dictate where the cutoff is in the unfilled data
|
|
347
|
+
*/
|
|
348
|
+
daysUnfilled?: number;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export interface UserProfile {
|
|
352
|
+
name?: string;
|
|
353
|
+
email?: string;
|
|
354
|
+
avatar?: string;
|
|
355
|
+
created: string | undefined;
|
|
356
|
+
distinct_id: string;
|
|
357
|
+
[key: string]: ValueValid;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export interface Person {
|
|
361
|
+
name: string;
|
|
362
|
+
email?: string;
|
|
363
|
+
avatar?: string;
|
|
364
|
+
created: string | undefined;
|
|
365
|
+
anonymousIds: string[];
|
|
366
|
+
sessionIds: string[];
|
|
367
|
+
distinct_id?: string;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* the generated user data
|
|
372
|
+
*/
|
|
373
|
+
export interface LookupTableSchema {
|
|
374
|
+
key: string;
|
|
375
|
+
entries: number;
|
|
376
|
+
attributes: Record<string, ValueValid>;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export interface LookupTableData {
|
|
380
|
+
key: string;
|
|
381
|
+
data: any[];
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export interface SCDSchema {
|
|
385
|
+
distinct_id: string;
|
|
386
|
+
insertTime: string;
|
|
387
|
+
startTime: string;
|
|
388
|
+
[key: string]: ValueValid;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export interface GroupProfileSchema {
|
|
392
|
+
key: string;
|
|
393
|
+
data: any[];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
--------------
|
|
397
|
+
|
|
398
|
+
here is now an implementation of the dungeon spec which shows how it's features work together; this is the type of spec you will be building based on the user's prompt:
|
|
399
|
+
|
|
400
|
+
--------------
|
|
401
|
+
|
|
402
|
+
import Chance from 'chance';
|
|
403
|
+
const chance = new Chance();
|
|
404
|
+
import dayjs from "dayjs";
|
|
405
|
+
import utc from "dayjs/plugin/utc.js";
|
|
406
|
+
dayjs.extend(utc);
|
|
407
|
+
import { uid, comma } from 'ak-tools';
|
|
408
|
+
import { pickAWinner, weighNumRange, date, integer, weighChoices } from '../lib/utils/utils.js';
|
|
409
|
+
|
|
410
|
+
const itemCategories = ["Books", "Movies", "Music", "Games", "Electronics", "Computers", "Smart Home", "Home", "Garden", "Pet", "Beauty", "Health", "Toys", "Kids", "Baby", "Handmade", "Sports", "Outdoors", "Automotive", "Industrial", "Entertainment", "Art", "Food", "Appliances", "Office", "Wedding", "Software"];
|
|
411
|
+
|
|
412
|
+
const videoCategories = ["funny", "educational", "inspirational", "music", "news", "sports", "cooking", "DIY", "travel", "gaming"];
|
|
413
|
+
|
|
414
|
+
/** @type {import('../types').Dungeon} */
|
|
415
|
+
const simple_dungeon = {
|
|
416
|
+
token: "",
|
|
417
|
+
seed: "simple is best",
|
|
418
|
+
numDays: 30, //how many days worth1 of data
|
|
419
|
+
numEvents: 50000, //how many events
|
|
420
|
+
numUsers: 500, //how many users
|
|
421
|
+
format: 'csv', //csv or json
|
|
422
|
+
region: "US",
|
|
423
|
+
hasAnonIds: false, //if true, anonymousIds are created for each user
|
|
424
|
+
hasSessionIds: false, //if true, hasSessionIds are created for each user
|
|
425
|
+
hasAdSpend: false,
|
|
426
|
+
makeChart: false,
|
|
427
|
+
hasLocation: true,
|
|
428
|
+
hasAndroidDevices: true,
|
|
429
|
+
hasIOSDevices: true,
|
|
430
|
+
hasDesktopDevices: true,
|
|
431
|
+
hasBrowser: true,
|
|
432
|
+
hasCampaigns: true,
|
|
433
|
+
isAnonymous: false,
|
|
434
|
+
concurrency: 10,
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
events: [
|
|
438
|
+
{
|
|
439
|
+
event: "checkout",
|
|
440
|
+
weight: 2,
|
|
441
|
+
properties: {
|
|
442
|
+
amount: weighNumRange(5, 500, .25),
|
|
443
|
+
currency: pickAWinner(["USD", "CAD", "EUR", "BTC", "ETH", "JPY"], 0),
|
|
444
|
+
coupon: weighChoices(["none", "none", "none", "none", "10%OFF", "20%OFF", "10%OFF", "20%OFF", "30%OFF", "40%OFF", "50%OFF"]),
|
|
445
|
+
numItems: weighNumRange(1, 10),
|
|
446
|
+
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
event: "add to cart",
|
|
451
|
+
weight: 4,
|
|
452
|
+
properties: {
|
|
453
|
+
amount: weighNumRange(5, 500, .25),
|
|
454
|
+
rating: weighNumRange(1, 5),
|
|
455
|
+
reviews: weighNumRange(0, 35),
|
|
456
|
+
isFeaturedItem: [true, false, false],
|
|
457
|
+
itemCategory: pickAWinner(itemCategories, integer(0, 27)),
|
|
458
|
+
dateItemListed: date(30, true, 'YYYY-MM-DD'),
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
event: "page view",
|
|
463
|
+
weight: 10,
|
|
464
|
+
properties: {
|
|
465
|
+
page: pickAWinner(["/", "/", "/help", "/account", "/watch", "/listen", "/product", "/people", "/peace"]),
|
|
466
|
+
utm_source: pickAWinner(["$organic", "$organic", "$organic", "$organic", "google", "google", "google", "facebook", "facebook", "twitter", "linkedin"]),
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
event: "watch video",
|
|
471
|
+
weight: 8,
|
|
472
|
+
properties: {
|
|
473
|
+
videoCategory: pickAWinner(videoCategories, integer(0, 9)),
|
|
474
|
+
isFeaturedItem: [true, false, false],
|
|
475
|
+
watchTimeSec: weighNumRange(10, 600, .25),
|
|
476
|
+
quality: ["2160p", "1440p", "1080p", "720p", "480p", "360p", "240p"],
|
|
477
|
+
format: ["mp4", "avi", "mov", "mpg"],
|
|
478
|
+
uploader_id: chance.guid.bind(chance)
|
|
479
|
+
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
event: "view item",
|
|
484
|
+
weight: 8,
|
|
485
|
+
properties: {
|
|
486
|
+
isFeaturedItem: [true, false, false],
|
|
487
|
+
itemCategory: pickAWinner(itemCategories, integer(0, 27)),
|
|
488
|
+
dateItemListed: date(30, true, 'YYYY-MM-DD'),
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
event: "save item",
|
|
493
|
+
weight: 5,
|
|
494
|
+
properties: {
|
|
495
|
+
isFeaturedItem: [true, false, false],
|
|
496
|
+
itemCategory: pickAWinner(itemCategories, integer(0, 27)),
|
|
497
|
+
dateItemListed: date(30, true, 'YYYY-MM-DD'),
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
event: "sign up",
|
|
502
|
+
isFirstEvent: true,
|
|
503
|
+
weight: 0,
|
|
504
|
+
properties: {
|
|
505
|
+
variants: ["A", "B", "C", "Control"],
|
|
506
|
+
flows: ["new", "existing", "loyal", "churned"],
|
|
507
|
+
flags: ["on", "off"],
|
|
508
|
+
experiment_ids: ["1234", "5678", "9012", "3456", "7890"],
|
|
509
|
+
multiVariate: [true, false]
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
],
|
|
513
|
+
superProps: {
|
|
514
|
+
theme: pickAWinner(["light", "dark", "custom", "light", "dark"]),
|
|
515
|
+
},
|
|
516
|
+
/*
|
|
517
|
+
user properties work the same as event properties
|
|
518
|
+
each key should be an array or function reference
|
|
519
|
+
*/
|
|
520
|
+
userProps: {
|
|
521
|
+
title: chance.profession.bind(chance),
|
|
522
|
+
luckyNumber: weighNumRange(42, 420, .3),
|
|
523
|
+
spiritAnimal: pickAWinner(["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"])
|
|
524
|
+
},
|
|
525
|
+
scdProps: {},
|
|
526
|
+
mirrorProps: {},
|
|
527
|
+
|
|
528
|
+
/*
|
|
529
|
+
for group analytics keys, we need an array of arrays [[],[],[]]
|
|
530
|
+
each pair represents a group_key and the number of profiles for that key
|
|
531
|
+
*/
|
|
532
|
+
groupKeys: [],
|
|
533
|
+
groupProps: {},
|
|
534
|
+
lookupTables: [],
|
|
535
|
+
hook: function (record, type, meta) {
|
|
536
|
+
if (type === "everything") {
|
|
537
|
+
|
|
538
|
+
//custom themers purchase more:
|
|
539
|
+
const numCustomMode = record.filter(a => a.theme === 'custom').length;
|
|
540
|
+
const numLightMode = record.filter(a => a.theme === 'light').length;
|
|
541
|
+
const numDarkMode = record.filter(a => a.theme === 'dark').length;
|
|
542
|
+
if (numCustomMode > numLightMode || numCustomMode > numDarkMode) {
|
|
543
|
+
//triple their checkout events
|
|
544
|
+
const checkoutEvents = record.filter(a => a.event === 'checkout');
|
|
545
|
+
const newCheckouts = checkoutEvents.map(a => {
|
|
546
|
+
const randomInt = integer(-48, 48);
|
|
547
|
+
const newCheckout = {
|
|
548
|
+
...a,
|
|
549
|
+
time: dayjs(a.time).add(randomInt, 'hour').toISOString(),
|
|
550
|
+
event: "checkout",
|
|
551
|
+
amount: a.amount * 2,
|
|
552
|
+
coupon: "50%OFF"
|
|
553
|
+
};
|
|
554
|
+
return newCheckout;
|
|
555
|
+
});
|
|
556
|
+
record.push(...newCheckouts);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
//users who watch low quality videos churn more:
|
|
560
|
+
const loQuality = ["480p", "360p", "240p"];
|
|
561
|
+
const lowQualityWatches = record.filter(a => a.event === 'watch video' && loQuality.includes(a.quality));
|
|
562
|
+
const highQualityWatches = record.filter(a => a.event === 'watch video' && !loQuality.includes(a.quality));
|
|
563
|
+
if (lowQualityWatches.length > highQualityWatches.length) {
|
|
564
|
+
if (flip()) {
|
|
565
|
+
// find midpoint of records
|
|
566
|
+
const midpoint = Math.floor(record.length / 2);
|
|
567
|
+
record = record.slice(0, midpoint);
|
|
568
|
+
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
return record;
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
function flip(likelihood = 50) {
|
|
581
|
+
return chance.bool({ likelihood });
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
export default simple_dungeon;
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
--------------
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
your job is to build dungeons. that is all you do and all your reply with.
|
|
592
|
+
|
|
593
|
+
the user will provide you with a prompt, and you will generate a spec for a dungeon based on that prompt to the best of your ability.
|