@ak--47/dungeon-master 1.0.0
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/README.md +518 -0
- package/dungeons/array-of-object-lookup-schema.json +327 -0
- package/dungeons/array-of-object-lookup.js +220 -0
- package/dungeons/ecommerce-schema.json +462 -0
- package/dungeons/ecommerce.js +447 -0
- package/dungeons/education-schema.json +2409 -0
- package/dungeons/education.js +768 -0
- package/dungeons/fintech-schema.json +14034 -0
- package/dungeons/fintech.js +696 -0
- package/dungeons/foobar-schema.json +403 -0
- package/dungeons/foobar.js +296 -0
- package/dungeons/food-delivery-schema.json +192 -0
- package/dungeons/food-delivery.js +602 -0
- package/dungeons/food-schema.json +1152 -0
- package/dungeons/food.js +754 -0
- package/dungeons/gaming-schema.json +1270 -0
- package/dungeons/gaming.js +508 -0
- package/dungeons/insurance-application-schema.json +204 -0
- package/dungeons/insurance-application.js +605 -0
- package/dungeons/media-schema.json +906 -0
- package/dungeons/media.js +790 -0
- package/dungeons/retention-cadence-schema.json +78 -0
- package/dungeons/retention-cadence.js +244 -0
- package/dungeons/rpg-schema.json +4526 -0
- package/dungeons/rpg.js +919 -0
- package/dungeons/sanity-schema.json +255 -0
- package/dungeons/sanity.js +152 -0
- package/dungeons/sass-schema.json +1291 -0
- package/dungeons/sass.js +795 -0
- package/dungeons/scd-schema.json +919 -0
- package/dungeons/scd.js +277 -0
- package/dungeons/simple-schema.json +608 -0
- package/dungeons/simple.js +285 -0
- package/dungeons/simplest-schema.json +1418 -0
- package/dungeons/simplest.js +392 -0
- package/dungeons/social-schema.json +1118 -0
- package/dungeons/social.js +686 -0
- package/dungeons/text-generation-schema.json +3096 -0
- package/dungeons/text-generation.js +812 -0
- package/index.js +567 -0
- package/lib/core/config-validator.js +395 -0
- package/lib/core/context.js +204 -0
- package/lib/core/dungeon-loader.js +337 -0
- package/lib/core/storage.js +379 -0
- package/lib/generators/adspend.js +132 -0
- package/lib/generators/events.js +271 -0
- package/lib/generators/funnels.js +407 -0
- package/lib/generators/mirror.js +167 -0
- package/lib/generators/product-lookup.js +262 -0
- package/lib/generators/product-names.js +195 -0
- package/lib/generators/profiles.js +93 -0
- package/lib/generators/scd.js +124 -0
- package/lib/generators/text.js +1192 -0
- package/lib/orchestrators/mixpanel-sender.js +266 -0
- package/lib/orchestrators/user-loop.js +335 -0
- package/lib/templates/abbreviated.d.ts +169 -0
- package/lib/templates/defaults.js +1405 -0
- package/lib/templates/phrases.js +2526 -0
- package/lib/templates/schema.d.ts +173 -0
- package/lib/templates/soup-presets.js +188 -0
- package/lib/utils/function-registry.js +302 -0
- package/lib/utils/json-evaluator.js +172 -0
- package/lib/utils/logger.js +34 -0
- package/lib/utils/utils.js +1490 -0
- package/package.json +89 -0
- package/types.d.ts +865 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ad Spend generator module
|
|
3
|
+
* Creates realistic advertising spend events with UTM parameters and metrics
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @typedef {import('../../types').Context} Context */
|
|
7
|
+
|
|
8
|
+
import dayjs from "dayjs";
|
|
9
|
+
import * as u from "../utils/utils.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates ad spend events for a given day and campaign configurations
|
|
13
|
+
* @param {Context} context - Context object containing config, defaults, etc.
|
|
14
|
+
* @param {string} day - ISO date string for the ad spend day
|
|
15
|
+
* @param {Array} campaigns - Array of campaign configurations (optional, uses context.campaigns if not provided)
|
|
16
|
+
* @returns {Promise<Array>} Array of ad spend event objects
|
|
17
|
+
*/
|
|
18
|
+
export async function makeAdSpend(context, day, campaigns = null) {
|
|
19
|
+
// Update operation counter
|
|
20
|
+
context.incrementOperations();
|
|
21
|
+
|
|
22
|
+
// Use campaigns from context if not provided
|
|
23
|
+
const campaignConfigs = campaigns || context.campaigns;
|
|
24
|
+
|
|
25
|
+
if (!campaignConfigs || campaignConfigs.length === 0) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const chance = u.getChance();
|
|
30
|
+
const adSpendEvents = [];
|
|
31
|
+
|
|
32
|
+
for (const network of campaignConfigs) {
|
|
33
|
+
const networkCampaigns = network.utm_campaign;
|
|
34
|
+
|
|
35
|
+
for (const campaign of networkCampaigns) {
|
|
36
|
+
// Skip organic campaigns
|
|
37
|
+
if (campaign === "$organic") continue;
|
|
38
|
+
|
|
39
|
+
// Generate realistic ad spend metrics
|
|
40
|
+
const adSpendEvent = createAdSpendEvent(network, campaign, day, chance);
|
|
41
|
+
adSpendEvents.push(adSpendEvent);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return adSpendEvents;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a single ad spend event with realistic metrics
|
|
50
|
+
* @param {Object} network - Network configuration object
|
|
51
|
+
* @param {string} campaign - Campaign name
|
|
52
|
+
* @param {string} day - ISO date string
|
|
53
|
+
* @param {Object} chance - Chance.js instance
|
|
54
|
+
* @returns {Object} Ad spend event object
|
|
55
|
+
*/
|
|
56
|
+
function createAdSpendEvent(network, campaign, day, chance) {
|
|
57
|
+
// Generate realistic cost
|
|
58
|
+
const cost = chance.floating({ min: 10, max: 250, fixed: 2 });
|
|
59
|
+
|
|
60
|
+
// Generate realistic CPC and CTR
|
|
61
|
+
const avgCPC = chance.floating({ min: 0.33, max: 2.00, fixed: 4 });
|
|
62
|
+
const avgCTR = chance.floating({ min: 0.05, max: 0.25, fixed: 4 });
|
|
63
|
+
|
|
64
|
+
// Calculate derived metrics
|
|
65
|
+
const clicks = Math.floor(cost / avgCPC);
|
|
66
|
+
const impressions = Math.floor(clicks / avgCTR);
|
|
67
|
+
const views = Math.floor(impressions * avgCTR);
|
|
68
|
+
|
|
69
|
+
// Generate UTM parameters
|
|
70
|
+
const utm_medium = u.choose(u.pickAWinner(network.utm_medium)());
|
|
71
|
+
const utm_content = u.choose(u.pickAWinner(network.utm_content)());
|
|
72
|
+
const utm_term = u.choose(u.pickAWinner(network.utm_term)());
|
|
73
|
+
|
|
74
|
+
// Create unique identifiers
|
|
75
|
+
const id = network.utm_source[0] + '-' + campaign;
|
|
76
|
+
const uid = u.quickHash(id);
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
event: "$ad_spend",
|
|
80
|
+
time: day,
|
|
81
|
+
// source: 'dm4',
|
|
82
|
+
utm_campaign: campaign,
|
|
83
|
+
campaign_id: id,
|
|
84
|
+
insert_id: uid,
|
|
85
|
+
network: network.utm_source[0].toUpperCase(),
|
|
86
|
+
distinct_id: network.utm_source[0].toUpperCase(),
|
|
87
|
+
utm_source: network.utm_source[0],
|
|
88
|
+
utm_medium,
|
|
89
|
+
utm_content,
|
|
90
|
+
utm_term,
|
|
91
|
+
clicks,
|
|
92
|
+
views,
|
|
93
|
+
impressions,
|
|
94
|
+
cost,
|
|
95
|
+
date: dayjs(day).format("YYYY-MM-DD"),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Validates campaign configuration
|
|
101
|
+
* @param {Array} campaigns - Campaign configurations to validate
|
|
102
|
+
* @returns {boolean} True if valid, throws error if invalid
|
|
103
|
+
*/
|
|
104
|
+
export function validateCampaigns(campaigns) {
|
|
105
|
+
if (!Array.isArray(campaigns)) {
|
|
106
|
+
throw new Error("Campaigns must be an array");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const network of campaigns) {
|
|
110
|
+
if (!network.utm_source || !Array.isArray(network.utm_source)) {
|
|
111
|
+
throw new Error("Each campaign network must have utm_source array");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!network.utm_campaign || !Array.isArray(network.utm_campaign)) {
|
|
115
|
+
throw new Error("Each campaign network must have utm_campaign array");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!network.utm_medium || !Array.isArray(network.utm_medium)) {
|
|
119
|
+
throw new Error("Each campaign network must have utm_medium array");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!network.utm_content || !Array.isArray(network.utm_content)) {
|
|
123
|
+
throw new Error("Each campaign network must have utm_content array");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!network.utm_term || !Array.isArray(network.utm_term)) {
|
|
127
|
+
throw new Error("Each campaign network must have utm_term array");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event generator module
|
|
3
|
+
* Creates individual Mixpanel events with realistic properties and timing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @typedef {import('../../types').Dungeon} Config */
|
|
7
|
+
/** @typedef {import('../../types').EventConfig} EventConfig */
|
|
8
|
+
/** @typedef {import('../../types').ValueValid} ValueValid */
|
|
9
|
+
/** @typedef {import('../../types').EventSchema} EventSchema */
|
|
10
|
+
/** @typedef {import('../../types').Context} Context */
|
|
11
|
+
|
|
12
|
+
import dayjs from "dayjs";
|
|
13
|
+
import * as u from "../utils/utils.js";
|
|
14
|
+
import { dataLogger as logger } from "../utils/logger.js";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates a Mixpanel event with a flat shape
|
|
18
|
+
* @param {Context} context - Context object containing config, defaults, etc.
|
|
19
|
+
* @param {string} distinct_id - User identifier
|
|
20
|
+
* @param {number} earliestTime - Unix timestamp for earliest possible event time
|
|
21
|
+
* @param {Object} chosenEvent - Event configuration object
|
|
22
|
+
* @param {string[]} [anonymousIds] - Array of anonymous/device IDs
|
|
23
|
+
* @param {string[]} [sessionIds] - Array of session IDs
|
|
24
|
+
* @param {Object} [superProps] - Super properties to add to event
|
|
25
|
+
* @param {Array} [groupKeys] - Group key configurations
|
|
26
|
+
* @param {boolean} [isFirstEvent] - Whether this is the user's first event
|
|
27
|
+
* @param {boolean} [skipDefaults] - Whether to skip adding default properties
|
|
28
|
+
* @returns {Promise<Object>} Generated event object
|
|
29
|
+
*/
|
|
30
|
+
export async function makeEvent(
|
|
31
|
+
context,
|
|
32
|
+
distinct_id,
|
|
33
|
+
earliestTime,
|
|
34
|
+
chosenEvent,
|
|
35
|
+
anonymousIds = [],
|
|
36
|
+
sessionIds = [],
|
|
37
|
+
superProps = {},
|
|
38
|
+
groupKeys = [],
|
|
39
|
+
isFirstEvent = false,
|
|
40
|
+
skipDefaults = false
|
|
41
|
+
) {
|
|
42
|
+
// Validate required parameters
|
|
43
|
+
if (!distinct_id) throw new Error("no distinct_id");
|
|
44
|
+
if (!earliestTime) throw new Error("no earliestTime");
|
|
45
|
+
if (!chosenEvent) throw new Error("no chosenEvent");
|
|
46
|
+
|
|
47
|
+
// Update context metrics
|
|
48
|
+
context.incrementOperations();
|
|
49
|
+
context.incrementEvents();
|
|
50
|
+
|
|
51
|
+
const { config, defaults } = context;
|
|
52
|
+
const chance = u.getChance();
|
|
53
|
+
|
|
54
|
+
// Extract soup configuration for time distribution
|
|
55
|
+
// Dynamic peaks: enough to flatten DOW interference from chunk boundaries
|
|
56
|
+
const defaultPeaks = Math.max(5, (config.numDays || 30) * 2);
|
|
57
|
+
const { mean = 0, deviation = 2, peaks = defaultPeaks, dayOfWeekWeights, hourOfDayWeights } = /** @type {import('../../types').SoupConfig} */ (config.soup) || {};
|
|
58
|
+
|
|
59
|
+
// Extract feature flags from config
|
|
60
|
+
const {
|
|
61
|
+
hasAndroidDevices = false,
|
|
62
|
+
hasBrowser = false,
|
|
63
|
+
hasCampaigns = false,
|
|
64
|
+
hasDesktopDevices = false,
|
|
65
|
+
hasIOSDevices = false,
|
|
66
|
+
hasLocation = false
|
|
67
|
+
} = config;
|
|
68
|
+
|
|
69
|
+
// Create base event template
|
|
70
|
+
const eventTemplate = {
|
|
71
|
+
event: chosenEvent.event,
|
|
72
|
+
// source: "dm4",
|
|
73
|
+
time: "",
|
|
74
|
+
insert_id: "",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let defaultProps = {};
|
|
78
|
+
|
|
79
|
+
// Add default properties based on configuration
|
|
80
|
+
if (hasLocation) {
|
|
81
|
+
defaultProps.location = u.pickRandom(defaults.locationsEvents());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (hasBrowser) {
|
|
85
|
+
defaultProps.browser = u.choose(defaults.browsers());
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Add campaigns with attribution likelihood
|
|
89
|
+
if (hasCampaigns && chance.bool({ likelihood: 25 })) {
|
|
90
|
+
defaultProps.campaigns = u.pickRandom(defaults.campaigns());
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// PERFORMANCE: Use pre-computed device pool instead of rebuilding every time
|
|
94
|
+
if (defaults.allDevices.length) {
|
|
95
|
+
defaultProps.device = u.pickRandom(defaults.allDevices);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Set event time using TimeSoup for realistic distribution
|
|
99
|
+
if (earliestTime) {
|
|
100
|
+
let shiftedTimestamp;
|
|
101
|
+
if (isFirstEvent) {
|
|
102
|
+
shiftedTimestamp = earliestTime + context.TIME_SHIFT_SECONDS;
|
|
103
|
+
} else {
|
|
104
|
+
// TimeSoup returns unix seconds; shift and convert to ISO once
|
|
105
|
+
const soupTimestamp = u.TimeSoup(earliestTime, context.FIXED_NOW, peaks, deviation, mean, dayOfWeekWeights, hourOfDayWeights, context.TIME_SHIFT_SECONDS);
|
|
106
|
+
shiftedTimestamp = soupTimestamp + context.TIME_SHIFT_SECONDS;
|
|
107
|
+
}
|
|
108
|
+
// Drop events that would land in the future (Mixpanel rewrites these to "now", causing pile-ups)
|
|
109
|
+
if (shiftedTimestamp > context.MAX_TIME) {
|
|
110
|
+
eventTemplate._drop = true;
|
|
111
|
+
}
|
|
112
|
+
eventTemplate.time = dayjs.unix(Math.min(shiftedTimestamp, context.MAX_TIME)).toISOString();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Add anonymous and session identifiers
|
|
116
|
+
if (anonymousIds.length) {
|
|
117
|
+
eventTemplate.device_id = u.pickRandom(anonymousIds);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (sessionIds.length) {
|
|
121
|
+
eventTemplate.session_id = u.pickRandom(sessionIds);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Sometimes add user_id (for attribution modeling)
|
|
125
|
+
if (!isFirstEvent && chance.bool({ likelihood: 42 })) {
|
|
126
|
+
eventTemplate.user_id = distinct_id;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Ensure we have either user_id or device_id
|
|
130
|
+
if (!eventTemplate.user_id && !eventTemplate.device_id) {
|
|
131
|
+
eventTemplate.user_id = distinct_id;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// PERFORMANCE: Process properties directly without creating intermediate object
|
|
135
|
+
// Add custom properties from event configuration
|
|
136
|
+
if (chosenEvent.properties) {
|
|
137
|
+
const eventKeys = Object.keys(chosenEvent.properties);
|
|
138
|
+
for (let i = 0; i < eventKeys.length; i++) {
|
|
139
|
+
const key = eventKeys[i];
|
|
140
|
+
try {
|
|
141
|
+
eventTemplate[key] = u.choose(chosenEvent.properties[key]);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
logger.error({ err: e, key, event: chosenEvent.event }, `Error processing property ${key} in ${chosenEvent.event} event`);
|
|
144
|
+
// Continue processing other properties
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Add super properties (override event properties if needed)
|
|
150
|
+
if (superProps) {
|
|
151
|
+
const superKeys = Object.keys(superProps);
|
|
152
|
+
for (let i = 0; i < superKeys.length; i++) {
|
|
153
|
+
const key = superKeys[i];
|
|
154
|
+
try {
|
|
155
|
+
eventTemplate[key] = u.choose(superProps[key]);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
logger.error({ err: e, key }, `Error processing super property ${key}`);
|
|
158
|
+
// Continue processing other properties
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Add default properties if not skipped
|
|
164
|
+
if (!skipDefaults) {
|
|
165
|
+
addDefaultProperties(eventTemplate, defaultProps);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Add group properties
|
|
169
|
+
addGroupProperties(eventTemplate, groupKeys);
|
|
170
|
+
|
|
171
|
+
// Generate unique insert_id
|
|
172
|
+
const distinctId = eventTemplate.user_id || eventTemplate.device_id || eventTemplate.distinct_id || distinct_id;
|
|
173
|
+
const tuple = `${eventTemplate.event}-${eventTemplate.time}-${distinctId}`;
|
|
174
|
+
eventTemplate.insert_id = u.quickHash(tuple);
|
|
175
|
+
|
|
176
|
+
// Call hook if configured (before returning the event)
|
|
177
|
+
const { hook } = config;
|
|
178
|
+
if (hook) {
|
|
179
|
+
const hookedEvent = await hook(eventTemplate, "event", {
|
|
180
|
+
user: { distinct_id },
|
|
181
|
+
config
|
|
182
|
+
});
|
|
183
|
+
// If hook returns a modified event, use it; otherwise use original
|
|
184
|
+
if (hookedEvent && typeof hookedEvent === 'object') {
|
|
185
|
+
return hookedEvent;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Note: Time shift already applied above during timestamp calculation
|
|
190
|
+
|
|
191
|
+
return eventTemplate;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Adds default properties to an event template
|
|
196
|
+
* Handles complex nested property structures
|
|
197
|
+
* @param {Object} eventTemplate - Event object to modify
|
|
198
|
+
* @param {Object} defaultProps - Default properties to add
|
|
199
|
+
*/
|
|
200
|
+
function addDefaultProperties(eventTemplate, defaultProps) {
|
|
201
|
+
for (const key in defaultProps) {
|
|
202
|
+
if (Array.isArray(defaultProps[key])) {
|
|
203
|
+
const choice = u.choose(defaultProps[key]);
|
|
204
|
+
|
|
205
|
+
if (typeof choice === "string") {
|
|
206
|
+
if (!eventTemplate[key]) eventTemplate[key] = choice;
|
|
207
|
+
}
|
|
208
|
+
else if (Array.isArray(choice)) {
|
|
209
|
+
for (const subChoice of choice) {
|
|
210
|
+
if (!eventTemplate[key]) eventTemplate[key] = subChoice;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (typeof choice === "object") {
|
|
214
|
+
addNestedObjectProperties(eventTemplate, choice);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else if (typeof defaultProps[key] === "object") {
|
|
218
|
+
addNestedObjectProperties(eventTemplate, defaultProps[key]);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
if (!eventTemplate[key]) eventTemplate[key] = defaultProps[key];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Adds nested object properties to event template
|
|
228
|
+
* @param {Object} eventTemplate - Event object to modify
|
|
229
|
+
* @param {Object} obj - Object with properties to add
|
|
230
|
+
*/
|
|
231
|
+
function addNestedObjectProperties(eventTemplate, obj) {
|
|
232
|
+
for (const subKey in obj) {
|
|
233
|
+
if (typeof obj[subKey] === "string") {
|
|
234
|
+
if (!eventTemplate[subKey]) eventTemplate[subKey] = obj[subKey];
|
|
235
|
+
}
|
|
236
|
+
else if (Array.isArray(obj[subKey])) {
|
|
237
|
+
const subChoice = u.choose(obj[subKey]);
|
|
238
|
+
if (!eventTemplate[subKey]) eventTemplate[subKey] = subChoice;
|
|
239
|
+
}
|
|
240
|
+
else if (typeof obj[subKey] === "object") {
|
|
241
|
+
for (const subSubKey in obj[subKey]) {
|
|
242
|
+
if (!eventTemplate[subSubKey]) {
|
|
243
|
+
eventTemplate[subSubKey] = obj[subKey][subSubKey];
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Adds group properties to event based on group key configuration
|
|
252
|
+
* @param {Object} eventTemplate - Event object to modify
|
|
253
|
+
* @param {Array} groupKeys - Array of group key configurations
|
|
254
|
+
*/
|
|
255
|
+
function addGroupProperties(eventTemplate, groupKeys) {
|
|
256
|
+
for (const groupPair of groupKeys) {
|
|
257
|
+
const groupKey = groupPair[0];
|
|
258
|
+
const groupCardinality = groupPair[1];
|
|
259
|
+
const groupEvents = groupPair[2] || [];
|
|
260
|
+
|
|
261
|
+
// Empty array for group events means all events get the group property
|
|
262
|
+
if (!groupEvents.length) {
|
|
263
|
+
eventTemplate[groupKey] = String(u.pick(u.weighNumRange(1, groupCardinality)));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Only add group property if event is in the specified group events
|
|
267
|
+
if (groupEvents.includes(eventTemplate.event)) {
|
|
268
|
+
eventTemplate[groupKey] = String(u.pick(u.weighNumRange(1, groupCardinality)));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|