@ak--47/dungeon-master 1.2.2 → 1.3.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/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 -2
- 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 +2 -2
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +2 -2
- package/dungeons/vertical/ai-platform-schema.json +617 -0
- package/dungeons/vertical/ai-platform.js +799 -0
- package/dungeons/vertical/community.js +40 -26
- package/dungeons/vertical/crypto-schema.json +546 -0
- package/dungeons/vertical/crypto.js +721 -0
- package/dungeons/vertical/dating-schema.json +401 -0
- package/dungeons/vertical/dating.js +798 -0
- package/dungeons/vertical/devtools.js +13 -9
- package/dungeons/vertical/ecommerce.js +2 -3
- package/dungeons/vertical/education.js +4 -5
- package/dungeons/vertical/fintech.js +32 -29
- package/dungeons/vertical/fitness.js +2 -3
- package/dungeons/vertical/food-delivery.js +37 -43
- package/dungeons/vertical/gaming-schema.json +2495 -230
- package/dungeons/vertical/gaming.js +771 -388
- package/dungeons/vertical/healthcare.js +2 -3
- package/dungeons/vertical/insurance-application.js +2 -3
- package/dungeons/vertical/logistics.js +20 -14
- package/dungeons/vertical/marketplace.js +22 -14
- package/dungeons/vertical/media.js +39 -30
- package/dungeons/vertical/real-estate-schema.json +527 -0
- package/dungeons/vertical/real-estate.js +774 -0
- package/dungeons/vertical/sass.js +2 -3
- package/dungeons/vertical/social.js +15 -13
- package/dungeons/vertical/travel.js +59 -26
- package/lib/core/config-validator.js +71 -15
- package/lib/core/storage.js +14 -4
- package/lib/orchestrators/user-loop.js +39 -5
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/package.json +8 -2
- package/types.d.ts +219 -42
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const SEED = "dm4-devtools";
|
|
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 ──
|
|
@@ -256,7 +256,7 @@ const config = {
|
|
|
256
256
|
token,
|
|
257
257
|
seed: SEED,
|
|
258
258
|
numDays: num_days,
|
|
259
|
-
|
|
259
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
260
260
|
numUsers: num_users,
|
|
261
261
|
hasAnonIds: false,
|
|
262
262
|
hasSessionIds: true,
|
|
@@ -271,7 +271,6 @@ const config = {
|
|
|
271
271
|
hasCampaigns: false,
|
|
272
272
|
isAnonymous: false,
|
|
273
273
|
hasAdSpend: false,
|
|
274
|
-
percentUsersBornInDataset: 35,
|
|
275
274
|
hasAvatar: true,
|
|
276
275
|
concurrency: 1,
|
|
277
276
|
writeToDisk: false,
|
|
@@ -834,19 +833,24 @@ const config = {
|
|
|
834
833
|
}
|
|
835
834
|
|
|
836
835
|
// -- HOOK 6: POST-OUTAGE RECOVERY ---------------------
|
|
837
|
-
// After outage
|
|
838
|
-
|
|
839
|
-
|
|
836
|
+
// After the outage volume rebound (d44-48), deployment events get
|
|
837
|
+
// aggressively cloned to produce a visible spike above baseline.
|
|
838
|
+
// Shifted later than outage end (d42.25) so natural volume has
|
|
839
|
+
// recovered from the 0.05x suppression before cloning kicks in.
|
|
840
|
+
const RECOVERY_START = DATASET_START.add(44, "days");
|
|
841
|
+
const RECOVERY_END = DATASET_START.add(48, "days");
|
|
840
842
|
const deployEvents = events.filter(e => {
|
|
841
843
|
if (e.event !== "deployment completed") return false;
|
|
842
844
|
const t = dayjs(e.time);
|
|
843
|
-
return t.isAfter(
|
|
845
|
+
return t.isAfter(RECOVERY_START) && t.isBefore(RECOVERY_END);
|
|
844
846
|
});
|
|
845
847
|
deployEvents.forEach(dep => {
|
|
846
|
-
|
|
848
|
+
// 100% clone rate with 3 copies per event to clearly
|
|
849
|
+
// exceed baseline deploy volume (d35-41)
|
|
850
|
+
for (let c = 0; c < 3; c++) {
|
|
847
851
|
events.push({
|
|
848
852
|
...dep,
|
|
849
|
-
time: dayjs(dep.time).add(chance.integer({ min: 1, max:
|
|
853
|
+
time: dayjs(dep.time).add(chance.integer({ min: 1, max: 8 }), "hours").toISOString(),
|
|
850
854
|
user_id: dep.user_id,
|
|
851
855
|
deploy_status: chance.pickone(["success", "success", "rolled_back"]),
|
|
852
856
|
environment: "production",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const SEED = "simple is best";
|
|
3
3
|
const num_days = 108;
|
|
4
4
|
const num_users = 50_000;
|
|
5
|
-
const
|
|
5
|
+
const avg_events_per_user_per_day = 0.37;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
8
8
|
// ── env overrides ──
|
|
@@ -143,7 +143,7 @@ const config = {
|
|
|
143
143
|
token,
|
|
144
144
|
seed: SEED,
|
|
145
145
|
numDays: num_days,
|
|
146
|
-
|
|
146
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
147
147
|
numUsers: num_users,
|
|
148
148
|
format: 'json', //csv or json
|
|
149
149
|
region: "US",
|
|
@@ -159,7 +159,6 @@ const config = {
|
|
|
159
159
|
isAnonymous: false,
|
|
160
160
|
alsoInferFunnels: false,
|
|
161
161
|
concurrency: 1,
|
|
162
|
-
percentUsersBornInDataset: 25,
|
|
163
162
|
events: [
|
|
164
163
|
{
|
|
165
164
|
event: "checkout",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const SEED = "harness-education";
|
|
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 ──
|
|
@@ -193,7 +193,7 @@ const config = {
|
|
|
193
193
|
token,
|
|
194
194
|
seed: SEED,
|
|
195
195
|
numDays: num_days,
|
|
196
|
-
|
|
196
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
197
197
|
numUsers: num_users,
|
|
198
198
|
hasAnonIds: false,
|
|
199
199
|
hasSessionIds: true,
|
|
@@ -208,7 +208,6 @@ const config = {
|
|
|
208
208
|
hasCampaigns: false,
|
|
209
209
|
isAnonymous: false,
|
|
210
210
|
hasAdSpend: false,
|
|
211
|
-
percentUsersBornInDataset: 50,
|
|
212
211
|
hasAvatar: true,
|
|
213
212
|
concurrency: 1,
|
|
214
213
|
writeToDisk: false,
|
|
@@ -512,7 +511,7 @@ const config = {
|
|
|
512
511
|
* 8. PLAYBACK SPEED CORRELATION: Speed learners paradoxically score higher; thorough learners get extended time
|
|
513
512
|
*/
|
|
514
513
|
hook: function (record, type, meta) {
|
|
515
|
-
const NOW = dayjs();
|
|
514
|
+
const NOW = dayjs.utc();
|
|
516
515
|
const DATASET_START = NOW.subtract(num_days, 'days');
|
|
517
516
|
|
|
518
517
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -580,7 +579,7 @@ const config = {
|
|
|
580
579
|
// ═══════════════════════════════════════════════════════════════════
|
|
581
580
|
if (type === "event") {
|
|
582
581
|
if (record.time) {
|
|
583
|
-
const eventTime = dayjs(record.time);
|
|
582
|
+
const eventTime = dayjs.utc(record.time);
|
|
584
583
|
const dayInDataset = eventTime.diff(DATASET_START, 'days', true);
|
|
585
584
|
|
|
586
585
|
const spikableEvents = ["quiz started", "quiz completed", "assignment submitted"];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const SEED = "harness-fintech";
|
|
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 ──
|
|
@@ -139,7 +139,7 @@ const config = {
|
|
|
139
139
|
token,
|
|
140
140
|
seed: SEED,
|
|
141
141
|
numDays: num_days,
|
|
142
|
-
|
|
142
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
143
143
|
numUsers: num_users,
|
|
144
144
|
hasAnonIds: false,
|
|
145
145
|
hasSessionIds: true,
|
|
@@ -154,7 +154,6 @@ const config = {
|
|
|
154
154
|
hasCampaigns: false,
|
|
155
155
|
isAnonymous: false,
|
|
156
156
|
hasAdSpend: false,
|
|
157
|
-
percentUsersBornInDataset: 50,
|
|
158
157
|
|
|
159
158
|
hasAvatar: true,
|
|
160
159
|
|
|
@@ -534,32 +533,6 @@ const config = {
|
|
|
534
533
|
}
|
|
535
534
|
}
|
|
536
535
|
|
|
537
|
-
// ===============================================================
|
|
538
|
-
// Hook #7: PREMIUM TIER VALUE (event)
|
|
539
|
-
// Premium tier users get 3x reward values and 2x investment sell
|
|
540
|
-
// returns. Plus tier users get 1.5x rewards.
|
|
541
|
-
// ===============================================================
|
|
542
|
-
if (record.event === "reward redeemed") {
|
|
543
|
-
if (record.account_tier === "premium") {
|
|
544
|
-
record.value = Math.floor((record.value || 10) * 3);
|
|
545
|
-
record.premium_reward = true;
|
|
546
|
-
} else if (record.account_tier === "plus") {
|
|
547
|
-
record.value = Math.floor((record.value || 10) * 1.5);
|
|
548
|
-
record.premium_reward = false;
|
|
549
|
-
} else {
|
|
550
|
-
record.premium_reward = false;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
if (record.event === "investment made" && record.action === "sell") {
|
|
555
|
-
if (record.account_tier === "premium") {
|
|
556
|
-
record.amount = Math.floor((record.amount || 250) * 2);
|
|
557
|
-
record.premium_returns = true;
|
|
558
|
-
} else {
|
|
559
|
-
record.premium_returns = false;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
536
|
// ===============================================================
|
|
564
537
|
// Hook #8: MONTH-END ANXIETY (event)
|
|
565
538
|
// Last 3 days of month (day >= 28) see 40% longer app sessions
|
|
@@ -600,6 +573,36 @@ const config = {
|
|
|
600
573
|
e.low_balance_churn = profile.low_balance_churn;
|
|
601
574
|
});
|
|
602
575
|
|
|
576
|
+
// -----------------------------------------------------------
|
|
577
|
+
// Hook #7: PREMIUM TIER VALUE (moved from event hook)
|
|
578
|
+
// Uses profile.account_tier which is authoritative here.
|
|
579
|
+
// Premium: 3x rewards, 2x investment sell returns.
|
|
580
|
+
// Plus: 1.5x rewards.
|
|
581
|
+
// -----------------------------------------------------------
|
|
582
|
+
const tier = profile.account_tier;
|
|
583
|
+
userEvents.forEach(e => {
|
|
584
|
+
if (e.event === "reward redeemed") {
|
|
585
|
+
if (tier === "premium") {
|
|
586
|
+
e.value = Math.floor((e.value || 10) * 3);
|
|
587
|
+
e.premium_reward = true;
|
|
588
|
+
} else if (tier === "plus") {
|
|
589
|
+
e.value = Math.floor((e.value || 10) * 1.5);
|
|
590
|
+
e.premium_reward = false;
|
|
591
|
+
} else {
|
|
592
|
+
e.premium_reward = false;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (e.event === "investment made" && e.action === "sell") {
|
|
597
|
+
if (tier === "premium") {
|
|
598
|
+
e.amount = Math.floor((e.amount || 250) * 2);
|
|
599
|
+
e.premium_returns = true;
|
|
600
|
+
} else {
|
|
601
|
+
e.premium_returns = false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
603
606
|
const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
|
|
604
607
|
|
|
605
608
|
// -----------------------------------------------------------
|
|
@@ -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 ──
|
|
@@ -265,7 +265,7 @@ const config = {
|
|
|
265
265
|
token,
|
|
266
266
|
seed: SEED,
|
|
267
267
|
numDays: num_days,
|
|
268
|
-
|
|
268
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
269
269
|
numUsers: num_users,
|
|
270
270
|
hasAnonIds: false,
|
|
271
271
|
hasSessionIds: true,
|
|
@@ -280,7 +280,6 @@ const config = {
|
|
|
280
280
|
hasCampaigns: false,
|
|
281
281
|
isAnonymous: false,
|
|
282
282
|
hasAdSpend: false,
|
|
283
|
-
percentUsersBornInDataset: 35,
|
|
284
283
|
hasAvatar: true,
|
|
285
284
|
concurrency: 1,
|
|
286
285
|
writeToDisk: false,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const SEED = "harness-food";
|
|
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 ──
|
|
@@ -166,7 +166,7 @@ const config = {
|
|
|
166
166
|
token,
|
|
167
167
|
seed: SEED,
|
|
168
168
|
numDays: num_days,
|
|
169
|
-
|
|
169
|
+
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
170
170
|
numUsers: num_users,
|
|
171
171
|
hasAnonIds: false,
|
|
172
172
|
hasSessionIds: true,
|
|
@@ -181,7 +181,6 @@ const config = {
|
|
|
181
181
|
hasCampaigns: false,
|
|
182
182
|
isAnonymous: false,
|
|
183
183
|
hasAdSpend: false,
|
|
184
|
-
percentUsersBornInDataset: 50,
|
|
185
184
|
hasAvatar: true,
|
|
186
185
|
concurrency: 1,
|
|
187
186
|
writeToDisk: false,
|
|
@@ -586,46 +585,8 @@ const config = {
|
|
|
586
585
|
}
|
|
587
586
|
}
|
|
588
587
|
|
|
589
|
-
//
|
|
590
|
-
//
|
|
591
|
-
// Free-tier users get coupon_applied events spliced into their
|
|
592
|
-
// funnel sequences 30% of the time, simulating promotional nudges
|
|
593
|
-
// that push non-subscribers toward conversion.
|
|
594
|
-
// ═══════════════════════════════════════════════════════════════════
|
|
595
|
-
if (type === "funnel-post") {
|
|
596
|
-
if (Array.isArray(record) && record.length >= 2) {
|
|
597
|
-
// Check if user is Free tier from first event's super props
|
|
598
|
-
const firstEvent = record[0];
|
|
599
|
-
const isFreeUser = firstEvent && firstEvent.subscription_tier === "Free";
|
|
600
|
-
|
|
601
|
-
if (isFreeUser && chance.bool({ likelihood: 30 })) {
|
|
602
|
-
// Pick a random insertion point between funnel steps
|
|
603
|
-
const insertIdx = chance.integer({ min: 1, max: record.length - 1 });
|
|
604
|
-
const prevEvent = record[insertIdx - 1];
|
|
605
|
-
const nextEvent = record[insertIdx];
|
|
606
|
-
const midTime = dayjs(prevEvent.time).add(
|
|
607
|
-
dayjs(nextEvent.time).diff(dayjs(prevEvent.time)) / 2,
|
|
608
|
-
'milliseconds'
|
|
609
|
-
).toISOString();
|
|
610
|
-
|
|
611
|
-
const couponTemplate = record.find(e => e.event === "coupon applied");
|
|
612
|
-
const couponEvent = {
|
|
613
|
-
...(couponTemplate || firstEvent),
|
|
614
|
-
event: "coupon applied",
|
|
615
|
-
time: midTime,
|
|
616
|
-
user_id: firstEvent.user_id,
|
|
617
|
-
subscription_tier: firstEvent.subscription_tier,
|
|
618
|
-
Platform: firstEvent.Platform,
|
|
619
|
-
city: firstEvent.city,
|
|
620
|
-
coupon_code: chance.pickone(couponCodes),
|
|
621
|
-
discount_type: chance.pickone(["percent", "flat", "free_delivery"]),
|
|
622
|
-
discount_value: chance.integer({ min: 10, max: 30 }),
|
|
623
|
-
coupon_injected: true,
|
|
624
|
-
};
|
|
625
|
-
record.splice(insertIdx, 0, couponEvent);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
588
|
+
// (Hook #2 coupon injection moved to everything hook where
|
|
589
|
+
// meta.profile.subscription_tier is authoritative)
|
|
629
590
|
|
|
630
591
|
// ═══════════════════════════════════════════════════════════════════
|
|
631
592
|
// HOOK 3: LATE NIGHT MUNCHIES (event)
|
|
@@ -694,6 +655,39 @@ const config = {
|
|
|
694
655
|
});
|
|
695
656
|
}
|
|
696
657
|
|
|
658
|
+
// ── Hook #2: COUPON INJECTION (moved from funnel-post) ──
|
|
659
|
+
// Free-tier users get coupon_applied events injected 30% of the
|
|
660
|
+
// time near checkout events. Uses meta.profile.subscription_tier
|
|
661
|
+
// which is authoritative here.
|
|
662
|
+
if (profile && profile.subscription_tier === "Free") {
|
|
663
|
+
for (let i = userEvents.length - 1; i >= 1; i--) {
|
|
664
|
+
const evt = userEvents[i];
|
|
665
|
+
if (evt.event === "checkout started" && chance.bool({ likelihood: 30 })) {
|
|
666
|
+
const prevEvent = userEvents[i - 1];
|
|
667
|
+
const midTime = dayjs(prevEvent.time).add(
|
|
668
|
+
dayjs(evt.time).diff(dayjs(prevEvent.time)) / 2,
|
|
669
|
+
'milliseconds'
|
|
670
|
+
).toISOString();
|
|
671
|
+
|
|
672
|
+
const couponTemplate = userEvents.find(e => e.event === "coupon applied");
|
|
673
|
+
const couponEvent = {
|
|
674
|
+
...(couponTemplate || evt),
|
|
675
|
+
event: "coupon applied",
|
|
676
|
+
time: midTime,
|
|
677
|
+
user_id: evt.user_id,
|
|
678
|
+
subscription_tier: profile.subscription_tier,
|
|
679
|
+
Platform: profile.Platform,
|
|
680
|
+
city: profile.city,
|
|
681
|
+
coupon_code: chance.pickone(couponCodes),
|
|
682
|
+
discount_type: chance.pickone(["percent", "flat", "free_delivery"]),
|
|
683
|
+
discount_value: chance.integer({ min: 10, max: 30 }),
|
|
684
|
+
coupon_injected: true,
|
|
685
|
+
};
|
|
686
|
+
userEvents.splice(i, 0, couponEvent);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
697
691
|
// ── Bug 2 fix: Conversion filtering for lunch rush + first order ──
|
|
698
692
|
// Hook 1: Non-lunch/dinner-hour events → drop ~30% of final funnel step
|
|
699
693
|
// Hook 8: Returning users (no first_order_bonus) → drop ~30% of final funnel step
|