@ak--47/dungeon-master 1.2.3 → 1.3.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/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -4
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +5 -6
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +3 -3
- package/dungeons/vertical/ai-platform.js +858 -0
- package/dungeons/vertical/community.js +84 -40
- package/dungeons/vertical/crypto.js +830 -0
- package/dungeons/vertical/dating.js +744 -0
- package/dungeons/vertical/devtools.js +175 -69
- package/dungeons/vertical/ecommerce.js +242 -94
- package/dungeons/vertical/education.js +330 -313
- package/dungeons/vertical/fintech.js +442 -313
- package/dungeons/vertical/fitness.js +143 -61
- package/dungeons/vertical/food-delivery.js +327 -353
- package/dungeons/vertical/gaming.js +912 -382
- package/dungeons/vertical/healthcare.js +142 -63
- package/dungeons/vertical/insurance-application.js +170 -76
- package/dungeons/vertical/logistics.js +115 -20
- package/dungeons/vertical/marketplace.js +152 -58
- package/dungeons/vertical/media.js +248 -384
- package/dungeons/vertical/real-estate.js +781 -0
- package/dungeons/vertical/sass.js +255 -266
- package/dungeons/vertical/social.js +264 -206
- package/dungeons/vertical/travel.js +117 -41
- package/index.js +17 -17
- package/lib/core/config-validator.js +159 -31
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +11 -14
- package/lib/generators/funnels.js +12 -4
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/orchestrators/user-loop.js +81 -47
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/lib/utils/utils.js +71 -39
- package/package.json +8 -2
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +251 -51
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -438
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const SEED = "dm4-fitness";
|
|
3
3
|
const num_days = 100;
|
|
4
4
|
const num_users = 5_000;
|
|
5
|
-
const
|
|
5
|
+
const avg_events_per_user_per_day = 1.2;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
8
8
|
// ── env overrides ──
|
|
@@ -15,9 +15,6 @@ import * as u from "../../lib/utils/utils.js";
|
|
|
15
15
|
|
|
16
16
|
dayjs.extend(utc);
|
|
17
17
|
const chance = u.initChance(SEED);
|
|
18
|
-
const NOW = dayjs();
|
|
19
|
-
const DATASET_START = NOW.subtract(num_days, "days");
|
|
20
|
-
|
|
21
18
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
22
19
|
|
|
23
20
|
/**
|
|
@@ -50,29 +47,28 @@ const DATASET_START = NOW.subtract(num_days, "days");
|
|
|
50
47
|
|
|
51
48
|
/**
|
|
52
49
|
* ═══════════════════════════════════════════════════════════════
|
|
53
|
-
* ANALYTICS HOOKS (
|
|
50
|
+
* ANALYTICS HOOKS (10 hooks)
|
|
54
51
|
* ═══════════════════════════════════════════════════════════════
|
|
55
52
|
*
|
|
53
|
+
* NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable
|
|
54
|
+
* via raw-prop breakdowns (HOD, day, segment) or behavioral cohorts.
|
|
55
|
+
*
|
|
56
56
|
* ───────────────────────────────────────────────────────────────
|
|
57
|
-
* 1. MORNING WORKOUT BOOST (
|
|
57
|
+
* 1. MORNING WORKOUT BOOST (everything)
|
|
58
58
|
* ───────────────────────────────────────────────────────────────
|
|
59
59
|
*
|
|
60
|
-
* PATTERN: Workouts
|
|
61
|
-
*
|
|
62
|
-
* morning exercise.
|
|
60
|
+
* PATTERN: Workouts in 5-9 UTC get calories_burned 1.3x. Mutates
|
|
61
|
+
* raw prop. No flag.
|
|
63
62
|
*
|
|
64
63
|
* HOW TO FIND IT IN MIXPANEL:
|
|
65
64
|
*
|
|
66
|
-
* Report 1:
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* • Expected: is_morning_workout=true should show ~1.3x avg
|
|
72
|
-
* calories vs false (true ≈ 390, false ≈ 300)
|
|
65
|
+
* Report 1: Avg Calories Burned by Hour of Day
|
|
66
|
+
* - Event: "workout completed"
|
|
67
|
+
* - Measure: Average of "calories_burned"
|
|
68
|
+
* - Breakdown: hour of day
|
|
69
|
+
* - Expected: 5-9 hours show ~ 1.3x baseline
|
|
73
70
|
*
|
|
74
|
-
* REAL-WORLD ANALOGUE:
|
|
75
|
-
* as more effective for fat loss, driving engagement.
|
|
71
|
+
* REAL-WORLD ANALOGUE: Morning workouts get a metabolic boost.
|
|
76
72
|
*
|
|
77
73
|
* ───────────────────────────────────────────────────────────────
|
|
78
74
|
* 2. POST-LAUNCH AI COACHING LIFT (event hook)
|
|
@@ -244,28 +240,80 @@ const DATASET_START = NOW.subtract(num_days, "days");
|
|
|
244
240
|
* REAL-WORLD ANALOGUE: Annual gym memberships have higher
|
|
245
241
|
* utilization — sunk cost + commitment drives consistency.
|
|
246
242
|
*
|
|
243
|
+
* ───────────────────────────────────────────────────────────────
|
|
244
|
+
* 9. WORKOUT LOOP TIME-TO-CONVERT (funnel-post)
|
|
245
|
+
* ───────────────────────────────────────────────────────────────
|
|
246
|
+
*
|
|
247
|
+
* PATTERN: Annual + family subscribers complete the Workout Loop
|
|
248
|
+
* funnel 1.3x faster (factor 0.77); Free users 1.25x slower (factor 1.25).
|
|
249
|
+
*
|
|
250
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
251
|
+
*
|
|
252
|
+
* Report 1: Workout Loop Median Time-to-Convert by Subscription
|
|
253
|
+
* - Funnels > "workout planned" -> "workout completed" -> "progress checked"
|
|
254
|
+
* - Measure: Median time to convert
|
|
255
|
+
* - Breakdown: subscription_plan
|
|
256
|
+
* - Expected: annual ~ 0.77x; free ~ 1.25x
|
|
257
|
+
*
|
|
258
|
+
* NOTE (funnel-post measurement): visible only via Mixpanel funnel
|
|
259
|
+
* median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
|
|
260
|
+
* show this — funnel-post adjusts gaps within funnel instances, not
|
|
261
|
+
* across the user's full event history.
|
|
262
|
+
*
|
|
263
|
+
* ───────────────────────────────────────────────────────────────
|
|
264
|
+
* 10. WORKOUT-COUNT MAGIC NUMBER (everything)
|
|
265
|
+
* ───────────────────────────────────────────────────────────────
|
|
266
|
+
*
|
|
267
|
+
* PATTERN: Sweet 12-20 workouts/user → +35% on workout
|
|
268
|
+
* duration_minutes (peak progression). Over 21+ → drop 30% of
|
|
269
|
+
* post-day-30 events (overtraining churn). No flag.
|
|
270
|
+
*
|
|
271
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
272
|
+
*
|
|
273
|
+
* Report 1: Avg Workout Duration by Workout-Count Bucket
|
|
274
|
+
* - Cohort A: users with 12-20 "workout completed"
|
|
275
|
+
* - Cohort B: users with 0-11
|
|
276
|
+
* - Event: "workout completed"
|
|
277
|
+
* - Measure: Average of "duration_minutes"
|
|
278
|
+
* - Expected: A ~ 1.35x B
|
|
279
|
+
*
|
|
280
|
+
* Report 2: D30+ Activity on Heavy Workout Cohort
|
|
281
|
+
* - Cohort C: users with >= 21 "workout completed"
|
|
282
|
+
* - Cohort A: users with 12-20
|
|
283
|
+
* - Event: any event
|
|
284
|
+
* - Measure: Total per user, post-day-30
|
|
285
|
+
* - Expected: C ~ 30% fewer post-day-30 events per user
|
|
286
|
+
*
|
|
287
|
+
* REAL-WORLD ANALOGUE: Sweet-spot training drives progression;
|
|
288
|
+
* over-training causes injury and burnout.
|
|
289
|
+
*
|
|
247
290
|
* ═══════════════════════════════════════════════════════════════
|
|
248
291
|
* EXPECTED METRICS SUMMARY
|
|
249
292
|
* ═══════════════════════════════════════════════════════════════
|
|
250
293
|
*
|
|
251
|
-
* Hook | Metric | Baseline | Effect
|
|
252
|
-
*
|
|
253
|
-
* Morning Workout Boost | calories_burned
|
|
254
|
-
* AI Coaching Lift | duration_minutes |
|
|
255
|
-
* Streak Retention | achievements/user |
|
|
256
|
-
* Social Challenge Completion | challenges/user |
|
|
257
|
-
* Resolver Churn Cliff | events after day 14 |
|
|
258
|
-
* Coach Session Quality | satisfaction_score
|
|
259
|
-
* Coach Profile Enrichment | total_workouts | 0 | 350
|
|
260
|
-
* Annual Funnel Lift | funnel conversion | 45% | 63%
|
|
294
|
+
* Hook | Metric | Baseline | Effect | Ratio
|
|
295
|
+
* ----------------------------|---------------------|----------|-----------|------
|
|
296
|
+
* Morning Workout Boost | calories_burned 5-9 | 1x | 1.3x | 1.3x
|
|
297
|
+
* AI Coaching Lift | duration_minutes | 1x | 1.2x | 1.2x
|
|
298
|
+
* Streak Retention | achievements/user | 1x | 2-3x | 2-3x
|
|
299
|
+
* Social Challenge Completion | challenges/user | 1x | 1.5x | 1.5x
|
|
300
|
+
* Resolver Churn Cliff | events after day 14 | 1x | 0.3x | -70%
|
|
301
|
+
* Coach Session Quality | satisfaction_score | 3.0 | 4.3 | 1.4x
|
|
302
|
+
* Coach Profile Enrichment | total_workouts | 0 | 350 | n/a
|
|
303
|
+
* Annual Funnel Lift | funnel conversion | 45% | 63% | 1.4x
|
|
304
|
+
* Workout Loop T2C | median min by tier | 1x | 0.77/1.25x| ~ 1.6x range
|
|
305
|
+
* Workout Magic Number | sweet duration_min | 1x | 1.35x | 1.35x
|
|
306
|
+
* Workout Magic Number | over D30+ activity | 1x | 0.7x | -30%
|
|
261
307
|
*/
|
|
262
308
|
|
|
263
309
|
/** @type {Config} */
|
|
264
310
|
const config = {
|
|
265
311
|
token,
|
|
266
312
|
seed: SEED,
|
|
267
|
-
|
|
268
|
-
|
|
313
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
314
|
+
datasetEnd: "2026-04-28T23:59:59Z",
|
|
315
|
+
// numDays: num_days,
|
|
316
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
269
317
|
numUsers: num_users,
|
|
270
318
|
hasAnonIds: false,
|
|
271
319
|
hasSessionIds: true,
|
|
@@ -280,7 +328,6 @@ const config = {
|
|
|
280
328
|
hasCampaigns: false,
|
|
281
329
|
isAnonymous: false,
|
|
282
330
|
hasAdSpend: false,
|
|
283
|
-
percentUsersBornInDataset: 35,
|
|
284
331
|
hasAvatar: true,
|
|
285
332
|
concurrency: 1,
|
|
286
333
|
writeToDisk: false,
|
|
@@ -312,7 +359,6 @@ const config = {
|
|
|
312
359
|
duration_minutes: u.weighNumRange(10, 90, 0.5, 40),
|
|
313
360
|
calories_burned: u.weighNumRange(50, 800, 0.4, 300),
|
|
314
361
|
heart_rate_avg: u.weighNumRange(80, 185, 0.5, 130),
|
|
315
|
-
is_morning_workout: [false],
|
|
316
362
|
satisfaction_score: u.weighNumRange(1, 5, 0.7, 3),
|
|
317
363
|
coaching_mode: ["self_guided"],
|
|
318
364
|
challenge_mode: ["solo"],
|
|
@@ -592,8 +638,8 @@ const config = {
|
|
|
592
638
|
{ name: "family", price: 149.99 },
|
|
593
639
|
],
|
|
594
640
|
lifecycle: {
|
|
595
|
-
trialToPayRate: 0.
|
|
596
|
-
upgradeRate: 0.
|
|
641
|
+
trialToPayRate: 0.65,
|
|
642
|
+
upgradeRate: 0.65,
|
|
597
643
|
downgradeRate: 0.03,
|
|
598
644
|
churnRate: 0.05,
|
|
599
645
|
winBackRate: 0.10,
|
|
@@ -678,8 +724,8 @@ const config = {
|
|
|
678
724
|
|
|
679
725
|
// ── Hook Function ──────────────────────────────────────
|
|
680
726
|
hook: function (record, type, meta) {
|
|
681
|
-
//
|
|
682
|
-
//
|
|
727
|
+
// HOOK 7: COACH PROFILE ENRICHMENT (user) — coach segment users
|
|
728
|
+
// get high total_workouts + streak_days.
|
|
683
729
|
if (type === "user") {
|
|
684
730
|
if (record.segment === "coach") {
|
|
685
731
|
record.total_workouts = chance.integer({ min: 200, max: 500 });
|
|
@@ -687,29 +733,35 @@ const config = {
|
|
|
687
733
|
}
|
|
688
734
|
}
|
|
689
735
|
|
|
690
|
-
//
|
|
691
|
-
//
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
if (
|
|
702
|
-
record.
|
|
703
|
-
|
|
704
|
-
|
|
736
|
+
// HOOK 9 (T2C): WORKOUT LOOP TIME-TO-CONVERT (funnel-post)
|
|
737
|
+
// Annual subscribers complete the Workout Loop funnel 1.3x faster
|
|
738
|
+
// (factor 0.77); Free users 1.25x slower (factor 1.25).
|
|
739
|
+
if (type === "funnel-post") {
|
|
740
|
+
const segment = meta?.profile?.subscription_plan;
|
|
741
|
+
if (Array.isArray(record) && record.length > 1) {
|
|
742
|
+
const factor = (
|
|
743
|
+
segment === "annual" || segment === "family" ? 0.77 :
|
|
744
|
+
segment === "free" ? 1.25 :
|
|
745
|
+
1.0
|
|
746
|
+
);
|
|
747
|
+
if (factor !== 1.0) {
|
|
748
|
+
for (let i = 1; i < record.length; i++) {
|
|
749
|
+
const prev = dayjs(record[i - 1].time);
|
|
750
|
+
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
751
|
+
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
705
752
|
}
|
|
706
753
|
}
|
|
707
754
|
}
|
|
755
|
+
}
|
|
708
756
|
|
|
757
|
+
// (HOOK 1: MORNING WORKOUT BOOST moved to everything hook — hour checks
|
|
758
|
+
// must run after bunchIntoSessions redistributes timestamps)
|
|
759
|
+
if (type === "event") {
|
|
760
|
+
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
709
761
|
// ── HOOK 2: POST-LAUNCH AI COACHING LIFT (event) ────
|
|
710
762
|
// After day 35, ai_assisted workouts get 1.2x duration.
|
|
711
763
|
if (record.event === "workout completed" || record.event === "workout planned") {
|
|
712
|
-
const AI_LAUNCH =
|
|
764
|
+
const AI_LAUNCH = datasetStart.add(35, "days");
|
|
713
765
|
const eventTime = dayjs(record.time);
|
|
714
766
|
if (eventTime.isAfter(AI_LAUNCH) && record.coaching_mode === "ai_assisted") {
|
|
715
767
|
if (record.duration_minutes) {
|
|
@@ -724,6 +776,7 @@ const config = {
|
|
|
724
776
|
|
|
725
777
|
// ── EVERYTHING HOOKS ─────────────────────────────────
|
|
726
778
|
if (type === "everything") {
|
|
779
|
+
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
727
780
|
let events = record;
|
|
728
781
|
if (!events.length) return record;
|
|
729
782
|
|
|
@@ -737,16 +790,26 @@ const config = {
|
|
|
737
790
|
});
|
|
738
791
|
}
|
|
739
792
|
|
|
793
|
+
// HOOK 1: MORNING WORKOUT BOOST — 5AM-9AM UTC workouts get
|
|
794
|
+
// calories_burned 1.3x. No flag — analyst breaks down by HOD.
|
|
795
|
+
events.forEach(e => {
|
|
796
|
+
if (e.event === "workout completed") {
|
|
797
|
+
const hour = new Date(e.time).getUTCHours();
|
|
798
|
+
if (hour >= 5 && hour < 9 && e.calories_burned) {
|
|
799
|
+
e.calories_burned = Math.floor(e.calories_burned * 1.3);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
|
|
740
804
|
// ── HOOK 8: ANNUAL SUBSCRIBER CONVERSION FILTER ─
|
|
741
805
|
// Free/monthly-tier users drop ~30% of "progress checked"
|
|
742
806
|
// (last step of Workout Loop funnel) to simulate lower conversion.
|
|
807
|
+
// NOTE: subscription field is named `subscription_plan` (set by the
|
|
808
|
+
// subscription feature in user-loop.js), NOT `subscription_tier`.
|
|
743
809
|
if (meta && meta.profile) {
|
|
744
|
-
const
|
|
745
|
-
if (
|
|
746
|
-
record = record.filter(e =>
|
|
747
|
-
if (e.event === "progress checked" && chance.bool({ likelihood: 30 })) return false;
|
|
748
|
-
return true;
|
|
749
|
-
});
|
|
810
|
+
const plan = meta.profile.subscription_plan;
|
|
811
|
+
if (plan !== "annual" && plan !== "family" && chance.bool({ likelihood: 30 })) {
|
|
812
|
+
record = record.filter(e => e.event !== "progress checked");
|
|
750
813
|
events = record;
|
|
751
814
|
}
|
|
752
815
|
}
|
|
@@ -804,7 +867,7 @@ const config = {
|
|
|
804
867
|
// ── HOOK 5: RESOLVER CHURN CLIFF ─────────────────
|
|
805
868
|
// Resolver segment users with <8 events lose 70% after day 14.
|
|
806
869
|
if (meta && meta.profile && meta.profile.segment === "resolver" && events.length < 8) {
|
|
807
|
-
const CHURN_CLIFF =
|
|
870
|
+
const CHURN_CLIFF = datasetStart.add(14, "days");
|
|
808
871
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
809
872
|
const eventTime = dayjs(events[i].time);
|
|
810
873
|
if (eventTime.isAfter(CHURN_CLIFF) && chance.bool({ likelihood: 70 })) {
|
|
@@ -813,8 +876,7 @@ const config = {
|
|
|
813
876
|
}
|
|
814
877
|
}
|
|
815
878
|
|
|
816
|
-
//
|
|
817
|
-
// Users with coach_session events get higher satisfaction.
|
|
879
|
+
// HOOK 6: COACH SESSION QUALITY — coach-session satisfaction 4-5.
|
|
818
880
|
const hasCoachSessions = events.some(e => e.event === "coach session");
|
|
819
881
|
if (hasCoachSessions) {
|
|
820
882
|
events.forEach(e => {
|
|
@@ -824,6 +886,26 @@ const config = {
|
|
|
824
886
|
});
|
|
825
887
|
}
|
|
826
888
|
|
|
889
|
+
// HOOK 10: WORKOUT-COUNT MAGIC NUMBER (no flags)
|
|
890
|
+
// Sweet 12-20 workouts → +35% on workout duration_minutes (peak
|
|
891
|
+
// progression). Over 21+ → drop 30% of post-day-30 events
|
|
892
|
+
// (overtraining → churn).
|
|
893
|
+
const workoutCount = events.filter(e => e.event === "workout completed").length;
|
|
894
|
+
if (workoutCount >= 12 && workoutCount <= 20) {
|
|
895
|
+
events.forEach(e => {
|
|
896
|
+
if (e.event === "workout completed" && typeof e.duration_minutes === "number") {
|
|
897
|
+
e.duration_minutes = Math.round(e.duration_minutes * 1.35);
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
} else if (workoutCount >= 21) {
|
|
901
|
+
const day30 = datasetStart.add(30, "days");
|
|
902
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
903
|
+
if (dayjs(events[i].time).isAfter(day30) && chance.bool({ likelihood: 30 })) {
|
|
904
|
+
events.splice(i, 1);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
|
|
827
909
|
return record;
|
|
828
910
|
}
|
|
829
911
|
|