make-mp-data 1.1.19 → 1.2.1
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/.vscode/settings.json +1 -0
- package/README.md +1 -1
- package/cli.js +19 -1
- package/index.js +49 -127
- package/{default.js → models/complex.js} +3 -3
- package/models/deepNest.js +102 -0
- package/models/simple.js +133 -0
- package/package.json +2 -2
- package/tests/e2e.test.js +98 -41
- package/tests/unit.test.js +155 -0
- package/timesoup.js +92 -0
- package/utils.js +67 -101
package/tests/e2e.test.js
CHANGED
|
@@ -8,66 +8,94 @@ require('dotenv').config();
|
|
|
8
8
|
const { execSync } = require("child_process");
|
|
9
9
|
const u = require('ak-tools');
|
|
10
10
|
|
|
11
|
-
const simple = require('../
|
|
11
|
+
const simple = require('../models/simple.js');
|
|
12
|
+
const complex = require('../models/complex.js');
|
|
13
|
+
const deep = require('../models/deepNest.js');
|
|
12
14
|
|
|
13
15
|
const timeout = 60000;
|
|
14
16
|
const testToken = process.env.TEST_TOKEN;
|
|
15
17
|
|
|
18
|
+
describe('module', () => {
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
test('works as module', async () => {
|
|
20
|
+
test('works as module (no config)', async () => {
|
|
20
21
|
console.log('MODULE TEST');
|
|
21
|
-
const results = await generate({ verbose:
|
|
22
|
+
const results = await generate({ verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
|
|
22
23
|
const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
|
|
23
24
|
expect(eventData.length).toBeGreaterThan(980);
|
|
24
25
|
expect(groupProfilesData.length).toBe(0);
|
|
25
26
|
expect(lookupTableData.length).toBe(0);
|
|
26
27
|
expect(scdTableData.length).toBeGreaterThan(200);
|
|
28
|
+
expect(userProfilesData.length).toBe(100);
|
|
29
|
+
|
|
30
|
+
}, timeout);
|
|
31
|
+
|
|
32
|
+
test('works as module (simple)', async () => {
|
|
33
|
+
console.log('MODULE TEST: SIMPLE');
|
|
34
|
+
const results = await generate({ ...simple, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
|
|
35
|
+
const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
|
|
36
|
+
expect(eventData.length).toBeGreaterThan(980);
|
|
37
|
+
expect(groupProfilesData.length).toBe(0);
|
|
38
|
+
expect(lookupTableData.length).toBe(0);
|
|
39
|
+
expect(scdTableData.length).toBe(0);
|
|
27
40
|
expect(userProfilesData.length).toBe(100);
|
|
28
41
|
|
|
29
42
|
}, timeout);
|
|
30
43
|
|
|
31
|
-
test('works as
|
|
32
|
-
console.log('
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
expect(
|
|
44
|
+
test('works as module (complex)', async () => {
|
|
45
|
+
console.log('MODULE TEST: COMPLEX');
|
|
46
|
+
const results = await generate({ ...complex, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
|
|
47
|
+
const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
|
|
48
|
+
expect(eventData.length).toBeGreaterThan(980);
|
|
49
|
+
expect(groupProfilesData[0]?.data?.length).toBe(350);
|
|
50
|
+
expect(lookupTableData.length).toBe(1);
|
|
51
|
+
expect(lookupTableData[0].data.length).toBe(1000);
|
|
52
|
+
expect(scdTableData.length).toBeGreaterThan(200);
|
|
53
|
+
expect(userProfilesData.length).toBe(100);
|
|
54
|
+
|
|
37
55
|
}, timeout);
|
|
38
56
|
|
|
39
|
-
test('
|
|
40
|
-
console.log('
|
|
41
|
-
const results = await generate({verbose:
|
|
42
|
-
const {
|
|
43
|
-
expect(
|
|
44
|
-
expect(
|
|
45
|
-
expect(
|
|
57
|
+
test('works as module (deep nest)', async () => {
|
|
58
|
+
console.log('MODULE TEST: DEEP NEST');
|
|
59
|
+
const results = await generate({ ...deep, verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it" });
|
|
60
|
+
const { eventData, groupProfilesData, lookupTableData, scdTableData, userProfilesData } = results;
|
|
61
|
+
expect(eventData.length).toBeGreaterThan(980);
|
|
62
|
+
expect(groupProfilesData.length).toBe(0);
|
|
63
|
+
expect(lookupTableData.length).toBe(0);
|
|
64
|
+
expect(scdTableData.length).toBeGreaterThan(200);
|
|
65
|
+
expect(userProfilesData.length).toBe(100);
|
|
66
|
+
|
|
46
67
|
}, timeout);
|
|
47
68
|
|
|
48
|
-
test('every record is valid', async () => {
|
|
49
|
-
console.log('VALIDATION TEST');
|
|
50
|
-
const results = await generate({verbose: false, writeToDisk: false, numEvents: 10000, numUsers: 500 });
|
|
51
|
-
const { eventData, userProfilesData } = results;
|
|
52
|
-
const areEventsValid = eventData.every(validateEvent);
|
|
53
|
-
const areUsersValid = userProfilesData.every(validateUser);
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
const invalidUsers = userProfilesData.filter(u => !validateUser(u));
|
|
70
|
+
});
|
|
57
71
|
|
|
58
|
-
|
|
59
|
-
|
|
72
|
+
describe('cli', () => {
|
|
73
|
+
test('works as CLI (complex)', async () => {
|
|
74
|
+
console.log('COMPLEX CLI TEST');
|
|
75
|
+
const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it" --complex`);
|
|
76
|
+
expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
|
|
77
|
+
const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
|
|
78
|
+
expect(csvs.length).toBe(5);
|
|
79
|
+
clearData();
|
|
60
80
|
}, timeout);
|
|
61
81
|
|
|
62
|
-
test('
|
|
63
|
-
console.log('
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
expect(
|
|
82
|
+
test('works as CLI (simple)', async () => {
|
|
83
|
+
console.log('simple CLI TEST');
|
|
84
|
+
const run = execSync(`node ./index.js --numEvents 1000 --numUsers 100 --seed "deal with it"`);
|
|
85
|
+
expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
|
|
86
|
+
const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
|
|
87
|
+
expect(csvs.length).toBe(2);
|
|
88
|
+
clearData();
|
|
89
|
+
}, timeout);
|
|
68
90
|
|
|
69
|
-
|
|
70
|
-
|
|
91
|
+
test('works as CLI (custom)', async () => {
|
|
92
|
+
console.log('custom CLI TEST');
|
|
93
|
+
const run = execSync(`node ./index.js ./models/deepNest.js`);
|
|
94
|
+
expect(run.toString().trim().includes('have a wonderful day :)')).toBe(true);
|
|
95
|
+
const csvs = (await u.ls('./data')).filter(a => a.includes('.csv'));
|
|
96
|
+
expect(csvs.length).toBe(3);
|
|
97
|
+
clearData();
|
|
98
|
+
}, timeout);
|
|
71
99
|
|
|
72
100
|
});
|
|
73
101
|
|
|
@@ -102,16 +130,47 @@ describe('options + tweaks', () => {
|
|
|
102
130
|
expect(anonIds.length).toBe(0);
|
|
103
131
|
}, timeout);
|
|
104
132
|
|
|
133
|
+
test('sends data to mixpanel', async () => {
|
|
134
|
+
console.log('NETWORK TEST');
|
|
135
|
+
const results = await generate({ verbose: true, writeToDisk: false, numEvents: 1100, numUsers: 100, seed: "deal with it", token: testToken });
|
|
136
|
+
const { events, users, groups } = results.import;
|
|
137
|
+
expect(events.success).toBeGreaterThan(980);
|
|
138
|
+
expect(users.success).toBe(100);
|
|
139
|
+
expect(groups.length).toBe(0);
|
|
140
|
+
}, timeout);
|
|
105
141
|
|
|
106
|
-
|
|
142
|
+
test('every record is valid', async () => {
|
|
143
|
+
console.log('VALIDATION TEST');
|
|
144
|
+
const results = await generate({ verbose: true, writeToDisk: false, numEvents: 10000, numUsers: 500 });
|
|
145
|
+
const { eventData, userProfilesData } = results;
|
|
146
|
+
const areEventsValid = eventData.every(validateEvent);
|
|
147
|
+
const areUsersValid = userProfilesData.every(validateUser);
|
|
107
148
|
|
|
149
|
+
const invalidEvents = eventData.filter(e => !validateEvent(e));
|
|
150
|
+
const invalidUsers = userProfilesData.filter(u => !validateUser(u));
|
|
108
151
|
|
|
152
|
+
expect(areEventsValid).toBe(true);
|
|
153
|
+
expect(areUsersValid).toBe(true);
|
|
154
|
+
}, timeout);
|
|
109
155
|
|
|
110
|
-
|
|
156
|
+
test('every date is valid', async () => {
|
|
157
|
+
console.log('DATE TEST');
|
|
158
|
+
const results = await generate({ ...simple, writeToDisk: false, verbose: true });
|
|
159
|
+
const { eventData } = results;
|
|
160
|
+
const invalidDates = eventData.filter(e => !validateTime(e.time));
|
|
161
|
+
expect(eventData.every(e => validateTime(e.time))).toBe(true);
|
|
162
|
+
|
|
163
|
+
}, timeout);
|
|
111
164
|
|
|
112
165
|
});
|
|
113
166
|
|
|
114
167
|
afterAll(() => {
|
|
168
|
+
clearData();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
//helpers
|
|
172
|
+
|
|
173
|
+
function clearData() {
|
|
115
174
|
try {
|
|
116
175
|
console.log('clearing...');
|
|
117
176
|
execSync(`npm run prune`);
|
|
@@ -120,9 +179,7 @@ afterAll(() => {
|
|
|
120
179
|
catch (err) {
|
|
121
180
|
console.log('error clearing files');
|
|
122
181
|
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
//helpers
|
|
182
|
+
}
|
|
126
183
|
|
|
127
184
|
function validateEvent(event) {
|
|
128
185
|
if (!event.event) return false;
|
package/tests/unit.test.js
CHANGED
|
@@ -7,6 +7,7 @@ const { timeSoup } = generate;
|
|
|
7
7
|
require('dotenv').config();
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
|
|
10
11
|
describe('timeSoup', () => {
|
|
11
12
|
test('always positive dates', () => {
|
|
12
13
|
const dates = [];
|
|
@@ -21,3 +22,157 @@ describe('timeSoup', () => {
|
|
|
21
22
|
|
|
22
23
|
});
|
|
23
24
|
});
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
const { applySkew, boxMullerRandom, choose, date, dates, day, exhaust, generateEmoji, getUniqueKeys, integer, makeHashTags, makeProducts, mapToRange, person, pick, range, weighList, weightedRange } = require('../utils');
|
|
29
|
+
|
|
30
|
+
describe('utils', () => {
|
|
31
|
+
|
|
32
|
+
test('pick: works', () => {
|
|
33
|
+
const array = [1, 2, 3];
|
|
34
|
+
const item = pick(array);
|
|
35
|
+
expect(array).toContain(item);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('pick: null', () => {
|
|
39
|
+
expect(pick(123)).toBe(123);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
test('integer: diff', () => {
|
|
45
|
+
const min = 5;
|
|
46
|
+
const max = 10;
|
|
47
|
+
const result = integer(min, max);
|
|
48
|
+
expect(result).toBeGreaterThanOrEqual(min);
|
|
49
|
+
expect(result).toBeLessThanOrEqual(max);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('integer: same', () => {
|
|
53
|
+
expect(integer(7, 7)).toBe(7);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
test('hashtags', () => {
|
|
59
|
+
const hashtags = makeHashTags();
|
|
60
|
+
expect(hashtags).toBeInstanceOf(Array);
|
|
61
|
+
expect(hashtags).not.toHaveLength(0);
|
|
62
|
+
hashtags.forEach(tag => {
|
|
63
|
+
expect(tag).toMatch(/^#/);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
test('person: fields', () => {
|
|
70
|
+
const generatedPerson = person();
|
|
71
|
+
expect(generatedPerson).toHaveProperty('$name');
|
|
72
|
+
expect(generatedPerson).toHaveProperty('$email');
|
|
73
|
+
expect(generatedPerson).toHaveProperty('$avatar');
|
|
74
|
+
expect(generatedPerson).toHaveProperty('anonymousIds');
|
|
75
|
+
expect(generatedPerson.anonymousIds).toBeInstanceOf(Array);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
test('date: past date', () => {
|
|
80
|
+
const pastDate = date(10, true, 'YYYY-MM-DD')();
|
|
81
|
+
expect(dayjs(pastDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
|
|
82
|
+
expect(dayjs(pastDate).isBefore(dayjs())).toBeTruthy();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('date: future date', () => {
|
|
86
|
+
const futureDate = date(10, false, 'YYYY-MM-DD')();
|
|
87
|
+
expect(dayjs(futureDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
|
|
88
|
+
expect(dayjs(futureDate).isAfter(dayjs())).toBeTruthy();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('dates: returns pairs of dates', () => {
|
|
92
|
+
const datePairs = dates(10, 3, 'YYYY-MM-DD');
|
|
93
|
+
expect(datePairs).toBeInstanceOf(Array);
|
|
94
|
+
expect(datePairs).toHaveLength(3);
|
|
95
|
+
datePairs.forEach(pair => {
|
|
96
|
+
expect(pair).toHaveLength(2);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('choose: choose from array', () => {
|
|
101
|
+
const options = ['apple', 'banana', 'cherry'];
|
|
102
|
+
const choice = choose(options);
|
|
103
|
+
expect(options).toContain(choice);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('choose: execute function', () => {
|
|
107
|
+
const result = choose(() => 'test');
|
|
108
|
+
expect(result).toBe('test');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('exhaust: exhaust array elements', () => {
|
|
112
|
+
const arr = [1, 2, 3];
|
|
113
|
+
const exhaustFn = exhaust([...arr]);
|
|
114
|
+
expect(exhaustFn()).toBe(1);
|
|
115
|
+
expect(exhaustFn()).toBe(2);
|
|
116
|
+
expect(exhaustFn()).toBe(3);
|
|
117
|
+
expect(exhaustFn()).toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('generateEmoji: returns string of emojis', () => {
|
|
121
|
+
const emojis = generateEmoji(5)();
|
|
122
|
+
expect(typeof emojis).toBe('string');
|
|
123
|
+
expect(emojis.split(', ').length).toBeLessThanOrEqual(5);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('getUniqueKeys: find unique keys', () => {
|
|
127
|
+
const objects = [{ a: 1, b: 2 }, { a: 3, c: 4 }, { a: 5, b: 6 }];
|
|
128
|
+
const uniqueKeys = getUniqueKeys(objects);
|
|
129
|
+
expect(uniqueKeys).toEqual(expect.arrayContaining(['a', 'b', 'c']));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
test('date: generates a valid date', () => {
|
|
134
|
+
const result = date();
|
|
135
|
+
expect(dayjs(result()).isValid()).toBe(true);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('dates: generates an array of date pairs', () => {
|
|
139
|
+
const result = dates();
|
|
140
|
+
expect(result).toBeInstanceOf(Array);
|
|
141
|
+
expect(result.length).toBe(5); // Assuming default numPairs is 5
|
|
142
|
+
result.forEach(pair => {
|
|
143
|
+
expect(pair).toBeInstanceOf(Array);
|
|
144
|
+
expect(pair.length).toBe(2);
|
|
145
|
+
expect(dayjs(pair[0]()).isValid()).toBe(true);
|
|
146
|
+
expect(dayjs(pair[1]()).isValid()).toBe(true);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test('day: generates a day within range', () => {
|
|
151
|
+
const start = '2020-01-01';
|
|
152
|
+
const end = '2020-01-30';
|
|
153
|
+
const result = day(start, end);
|
|
154
|
+
const dayResult = result(0, 9);
|
|
155
|
+
expect(dayjs(dayResult.day).isAfter(dayjs(dayResult.start))).toBe(true);
|
|
156
|
+
expect(dayjs(dayResult.day).isBefore(dayjs(dayResult.end))).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('exhaust: sequentially removes items from array', () => {
|
|
160
|
+
const arr = [1, 2, 3];
|
|
161
|
+
const next = exhaust(arr);
|
|
162
|
+
expect(next()).toBe(1);
|
|
163
|
+
expect(next()).toBe(2);
|
|
164
|
+
expect(next()).toBe(3);
|
|
165
|
+
expect(next()).toBe(undefined); // or whatever your implementation does after array is exhausted
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test('generateEmoji: generates correct format and length', () => {
|
|
169
|
+
const result = generateEmoji();
|
|
170
|
+
const emojis = result();
|
|
171
|
+
expect(typeof emojis).toBe('string');
|
|
172
|
+
const emojiArray = emojis.split(', ');
|
|
173
|
+
expect(emojiArray.length).toBeLessThanOrEqual(10); // Assuming max default is 10
|
|
174
|
+
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
});
|
package/timesoup.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const Chance = require("chance");
|
|
2
|
+
const chance = new Chance();
|
|
3
|
+
const dayjs = require("dayjs");
|
|
4
|
+
const utc = require("dayjs/plugin/utc");
|
|
5
|
+
dayjs.extend(utc);
|
|
6
|
+
const { integer } = require('./utils');
|
|
7
|
+
const NOW = dayjs().unix();
|
|
8
|
+
|
|
9
|
+
const PEAK_DAYS = [
|
|
10
|
+
dayjs().subtract(2, "day").unix(),
|
|
11
|
+
dayjs().subtract(3, "day").unix(),
|
|
12
|
+
dayjs().subtract(5, "day").unix(),
|
|
13
|
+
dayjs().subtract(7, "day").unix(),
|
|
14
|
+
dayjs().subtract(11, "day").unix(),
|
|
15
|
+
dayjs().subtract(13, "day").unix(),
|
|
16
|
+
dayjs().subtract(17, "day").unix(),
|
|
17
|
+
dayjs().subtract(19, "day").unix(),
|
|
18
|
+
dayjs().subtract(23, "day").unix(),
|
|
19
|
+
dayjs().subtract(29, "day").unix(),
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* essentially, a timestamp generator with a twist
|
|
25
|
+
* @param {number} earliestTime - The earliest timestamp in Unix format.
|
|
26
|
+
* @param {number} latestTime - The latest timestamp in Unix format.
|
|
27
|
+
* @param {Array} peakDays - Array of Unix timestamps representing the start of peak days.
|
|
28
|
+
* @returns {number} - The generated event timestamp in Unix format.
|
|
29
|
+
*/
|
|
30
|
+
function AKsTimeSoup(earliestTime, latestTime = NOW, peakDays = PEAK_DAYS) {
|
|
31
|
+
let chosenTime;
|
|
32
|
+
let eventTime;
|
|
33
|
+
let validTime = false;
|
|
34
|
+
|
|
35
|
+
if (typeof earliestTime !== "number") {
|
|
36
|
+
if (parseInt(earliestTime) > 0) earliestTime = parseInt(earliestTime);
|
|
37
|
+
if (dayjs(earliestTime).isValid()) earliestTime = dayjs(earliestTime).unix();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
while (!validTime) {
|
|
41
|
+
|
|
42
|
+
// Define business hours
|
|
43
|
+
const peakStartHour = 4; // 4 AM
|
|
44
|
+
const peakEndHour = 23; // 11 PM
|
|
45
|
+
const likelihoodOfPeakDay = chance.integer({ min: integer(5, 42), max: integer(43, 69) }); // Randomize likelihood with CHAOS!~~
|
|
46
|
+
|
|
47
|
+
// Select a day, with a preference for peak days
|
|
48
|
+
let selectedDay;
|
|
49
|
+
if (chance.bool({ likelihood: likelihoodOfPeakDay })) { // Randomized likelihood to pick a peak day
|
|
50
|
+
selectedDay = peakDays.length > 0 ? chance.pickone(peakDays) : integer(earliestTime, latestTime);
|
|
51
|
+
} else {
|
|
52
|
+
// Introduce minor peaks by allowing some events to still occur during business hours
|
|
53
|
+
selectedDay = chance.bool({ likelihood: integer(1, 42) })
|
|
54
|
+
? chance.pickone(peakDays)
|
|
55
|
+
: integer(earliestTime, latestTime);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Normalize selectedDay to the start of the day
|
|
59
|
+
selectedDay = dayjs.unix(selectedDay).startOf('day').unix();
|
|
60
|
+
|
|
61
|
+
// Generate a random time within business hours with a higher concentration in the middle of the period
|
|
62
|
+
const businessStart = dayjs.unix(selectedDay).hour(peakStartHour).minute(0).second(0).unix();
|
|
63
|
+
const businessEnd = dayjs.unix(selectedDay).hour(peakEndHour).minute(0).second(0).unix();
|
|
64
|
+
|
|
65
|
+
if (selectedDay === peakDays[0]) {
|
|
66
|
+
// Use a skewed distribution for peak days
|
|
67
|
+
eventTime = chance.normal({ mean: (businessEnd + businessStart) / integer(1, 4), dev: (businessEnd - businessStart) / integer(2, 8) });
|
|
68
|
+
} else {
|
|
69
|
+
// For non-peak days, use a uniform distribution to add noise
|
|
70
|
+
eventTime = integer(integer(businessStart, businessEnd), integer(businessStart, businessEnd));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// usually, ensure the event time is within business hours
|
|
74
|
+
if (chance.bool({ likelihood: 42 })) eventTime = Math.min(Math.max(eventTime, businessStart), businessEnd);
|
|
75
|
+
|
|
76
|
+
if (eventTime > 0) validTime = true;
|
|
77
|
+
const parsedTime = dayjs.unix(eventTime).toISOString();
|
|
78
|
+
if (!parsedTime.startsWith('20')) validTime = false;
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
chosenTime = dayjs.unix(eventTime).toISOString();
|
|
82
|
+
|
|
83
|
+
//should never get here
|
|
84
|
+
if (eventTime < 0) debugger;
|
|
85
|
+
if (!chosenTime.startsWith('20')) debugger;
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
return chosenTime;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
module.exports = AKsTimeSoup;
|