@ak--47/dungeon-master 1.1.0 → 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/dungeons/technical/ad-spend-schema.json +2 -2
- package/dungeons/technical/ad-spend.js +16 -5
- package/dungeons/technical/anonymous-users-schema.json +4 -3
- package/dungeons/technical/anonymous-users.js +16 -6
- package/dungeons/technical/array-of-object-lookup-schema.json +1 -1
- package/dungeons/technical/array-of-object-lookup.js +22 -6
- package/dungeons/technical/experiments-schema.json +25 -19
- package/dungeons/technical/experiments.js +24 -6
- package/dungeons/technical/foobar.js +29 -13
- package/dungeons/technical/group-analytics-schema.json +92 -0
- package/dungeons/technical/group-analytics.js +16 -6
- package/dungeons/technical/mirror-strategies-schema.json +4 -4
- package/dungeons/technical/mirror-strategies.js +16 -6
- package/dungeons/technical/nested-objects.js +16 -5
- package/dungeons/technical/retention-cadence.js +14 -9
- package/dungeons/technical/sanity-schema.json +22 -22
- package/dungeons/technical/sanity.js +26 -6
- package/dungeons/technical/scale-test.js +16 -5
- package/dungeons/technical/scd-schema.json +230 -33
- package/dungeons/technical/scd.js +25 -5
- package/dungeons/technical/simple-schema.json +132 -48
- package/dungeons/technical/simple.js +21 -6
- package/dungeons/technical/simplest-schema.json +168 -79
- package/dungeons/technical/simplest.js +24 -7
- package/dungeons/technical/text-generation-schema.json +7 -4
- package/dungeons/technical/text-generation.js +23 -8
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/user/shalini.js +847 -0
- package/dungeons/vertical/community-schema.json +71 -49
- package/dungeons/vertical/community.js +57 -30
- package/dungeons/vertical/devtools-schema.json +69 -48
- package/dungeons/vertical/devtools.js +58 -31
- package/dungeons/vertical/ecommerce-schema.json +126 -108
- package/dungeons/vertical/ecommerce.js +32 -8
- package/dungeons/vertical/education-schema.json +5261 -272
- package/dungeons/vertical/education.js +45 -22
- package/dungeons/vertical/fintech-schema.json +165 -98
- package/dungeons/vertical/fintech.js +43 -11
- package/dungeons/vertical/fitness-schema.json +69 -47
- package/dungeons/vertical/fitness.js +56 -24
- package/dungeons/vertical/food-delivery-schema.json +36237 -187
- package/dungeons/vertical/food-delivery.js +67 -15
- package/dungeons/vertical/gaming-schema.json +104 -60
- package/dungeons/vertical/gaming.js +61 -16
- package/dungeons/vertical/healthcare-schema.json +70 -48
- package/dungeons/vertical/healthcare.js +77 -42
- package/dungeons/vertical/insurance-application-schema.json +66 -47
- package/dungeons/vertical/insurance-application.js +34 -10
- package/dungeons/vertical/logistics-schema.json +60 -38
- package/dungeons/vertical/logistics.js +77 -43
- package/dungeons/vertical/marketplace-schema.json +65 -43
- package/dungeons/vertical/marketplace.js +52 -21
- package/dungeons/vertical/media-schema.json +4085 -123
- package/dungeons/vertical/media.js +65 -18
- package/dungeons/vertical/rpg-schema.json +1984 -147
- package/dungeons/vertical/rpg.js +47 -11
- package/dungeons/vertical/sass-schema.json +2622 -147
- package/dungeons/vertical/sass.js +40 -10
- package/dungeons/vertical/social-schema.json +146 -78
- package/dungeons/vertical/social.js +39 -10
- package/dungeons/vertical/travel-schema.json +57 -35
- package/dungeons/vertical/travel.js +58 -21
- package/index.js +4 -1
- package/lib/core/config-validator.js +13 -2
- package/lib/core/context.js +9 -0
- package/lib/core/storage.js +2 -0
- package/lib/generators/events.js +3 -5
- package/lib/orchestrators/user-loop.js +24 -1
- package/lib/utils/utils.js +129 -9
- package/package.json +1 -1
- package/scripts/extract-dungeon-schema.mjs +64 -18
- package/types.d.ts +8 -3
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
// ── TWEAK THESE ──
|
|
2
|
+
const SEED = "dm4-healthcare";
|
|
3
|
+
const num_days = 100;
|
|
4
|
+
const num_users = 5_000;
|
|
5
|
+
const avg_events_per_user = 120;
|
|
6
|
+
let token = "your-mixpanel-token";
|
|
7
|
+
|
|
8
|
+
// ── env overrides ──
|
|
9
|
+
if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
|
|
10
|
+
|
|
1
11
|
import dayjs from "dayjs";
|
|
2
12
|
import utc from "dayjs/plugin/utc.js";
|
|
3
13
|
import "dotenv/config";
|
|
4
14
|
import * as u from "../../lib/utils/utils.js";
|
|
5
15
|
import * as v from "ak-tools";
|
|
6
16
|
|
|
7
|
-
const SEED = "dm4-healthcare";
|
|
8
17
|
dayjs.extend(utc);
|
|
9
18
|
const chance = u.initChance(SEED);
|
|
10
|
-
const num_users = 5_000;
|
|
11
|
-
const days = 100;
|
|
12
19
|
const NOW = dayjs();
|
|
13
|
-
const DATASET_START = NOW.subtract(
|
|
20
|
+
const DATASET_START = NOW.subtract(num_days, "days");
|
|
14
21
|
|
|
15
22
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
16
23
|
|
|
@@ -199,22 +206,25 @@ const clinicIds = v.range(1, 25).map(() => `CLINIC_${v.uid(4)}`);
|
|
|
199
206
|
* and experience levels that affect patient matching.
|
|
200
207
|
*
|
|
201
208
|
* ───────────────────────────────────────────────────────────────
|
|
202
|
-
* 8.
|
|
209
|
+
* 8. FREE-TIER CONVERSION DROP (everything hook)
|
|
203
210
|
* ───────────────────────────────────────────────────────────────
|
|
204
211
|
*
|
|
205
|
-
* PATTERN:
|
|
206
|
-
*
|
|
212
|
+
* PATTERN: Free-tier users lose ~30% of "consultation completed"
|
|
213
|
+
* events (last step of the Booking to Consultation funnel).
|
|
214
|
+
* This is implemented via event filtering in the everything hook
|
|
215
|
+
* rather than conversionRate modification in funnel-pre, so the
|
|
216
|
+
* effect is not diluted by organic (non-funnel) events.
|
|
207
217
|
*
|
|
208
218
|
* HOW TO FIND IT IN MIXPANEL:
|
|
209
219
|
*
|
|
210
220
|
* Report 1: Booking Conversion by Tier
|
|
211
221
|
* • Report type: Funnels
|
|
212
222
|
* • Steps: "symptom search" → "appointment booked" → "consultation completed"
|
|
213
|
-
* • Breakdown: "subscription_tier"
|
|
214
|
-
* • Expected:
|
|
223
|
+
* • Breakdown: "subscription_tier"
|
|
224
|
+
* • Expected: free ≈ 28% vs basic/premium ≈ 40% conversion
|
|
215
225
|
*
|
|
216
|
-
* REAL-WORLD ANALOGUE:
|
|
217
|
-
*
|
|
226
|
+
* REAL-WORLD ANALOGUE: Free-tier patients face longer wait times
|
|
227
|
+
* and limited scheduling, reducing completed consultations.
|
|
218
228
|
*
|
|
219
229
|
* ═══════════════════════════════════════════════════════════════
|
|
220
230
|
* EXPECTED METRICS SUMMARY
|
|
@@ -229,15 +239,15 @@ const clinicIds = v.range(1, 25).map(() => `CLINIC_${v.uid(4)}`);
|
|
|
229
239
|
* Chronic Refill Chain | refills (chronic) | 1 | 3-4 | 3-4x
|
|
230
240
|
* Occasional No-Shows | booking→consult | 95% | 75% | 0.79x
|
|
231
241
|
* Doctor Specialization | years_experience | 5 | 22 | 4.4x
|
|
232
|
-
*
|
|
242
|
+
* Free-Tier Conversion Drop | funnel conversion | 40% | 28% | 0.7x
|
|
233
243
|
*/
|
|
234
244
|
|
|
235
245
|
/** @type {Config} */
|
|
236
246
|
const config = {
|
|
237
|
-
token
|
|
247
|
+
token,
|
|
238
248
|
seed: SEED,
|
|
239
|
-
numDays:
|
|
240
|
-
numEvents: num_users *
|
|
249
|
+
numDays: num_days,
|
|
250
|
+
numEvents: num_users * avg_events_per_user,
|
|
241
251
|
numUsers: num_users,
|
|
242
252
|
hasAnonIds: false,
|
|
243
253
|
hasSessionIds: true,
|
|
@@ -254,10 +264,16 @@ const config = {
|
|
|
254
264
|
hasAdSpend: false,
|
|
255
265
|
percentUsersBornInDataset: 35,
|
|
256
266
|
hasAvatar: true,
|
|
257
|
-
batchSize: 2_500_000,
|
|
258
267
|
concurrency: 1,
|
|
259
268
|
writeToDisk: false,
|
|
260
|
-
scdProps: {
|
|
269
|
+
scdProps: {
|
|
270
|
+
care_plan: {
|
|
271
|
+
values: ["preventive", "routine", "chronic", "acute"],
|
|
272
|
+
frequency: "month",
|
|
273
|
+
timing: "fuzzy",
|
|
274
|
+
max: 8
|
|
275
|
+
}
|
|
276
|
+
},
|
|
261
277
|
mirrorProps: {},
|
|
262
278
|
lookupTables: [],
|
|
263
279
|
|
|
@@ -479,7 +495,7 @@ const config = {
|
|
|
479
495
|
// ── SuperProps ──────────────────────────────────────────
|
|
480
496
|
superProps: {
|
|
481
497
|
subscription_tier: ["free", "free", "free", "basic", "basic", "premium"],
|
|
482
|
-
|
|
498
|
+
Platform: ["ios", "android", "web"],
|
|
483
499
|
},
|
|
484
500
|
|
|
485
501
|
// ── UserProps ──────────────────────────────────────────
|
|
@@ -490,6 +506,8 @@ const config = {
|
|
|
490
506
|
preferred_language: ["en", "en", "en", "en", "es", "pt", "de", "fr"],
|
|
491
507
|
has_chronic_condition: [false, false, false, true],
|
|
492
508
|
age_range: ["18-25", "26-35", "26-35", "36-45", "36-45", "46-55", "56-65", "65+"],
|
|
509
|
+
subscription_tier: ["free", "free", "free", "basic", "basic", "premium"],
|
|
510
|
+
Platform: ["ios", "android", "web"],
|
|
493
511
|
},
|
|
494
512
|
|
|
495
513
|
// ── Personas ──────────────────────────────────
|
|
@@ -639,16 +657,9 @@ const config = {
|
|
|
639
657
|
}
|
|
640
658
|
|
|
641
659
|
// ── HOOK 8: PREMIUM BOOKING FUNNEL LIFT (funnel-pre) ─
|
|
642
|
-
//
|
|
660
|
+
// (conversionRate boost removed — filtering applied in everything hook instead)
|
|
643
661
|
if (type === "funnel-pre") {
|
|
644
|
-
|
|
645
|
-
const tier = meta.profile.subscription_tier;
|
|
646
|
-
if (tier === "premium") {
|
|
647
|
-
record.conversionRate = Math.min(record.conversionRate * 1.4, 95);
|
|
648
|
-
} else if (tier === "basic") {
|
|
649
|
-
record.conversionRate = Math.min(record.conversionRate * 1.15, 90);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
662
|
+
// no-op: conversion differentiation handled via event filtering below
|
|
652
663
|
}
|
|
653
664
|
|
|
654
665
|
// ── HOOK 1: AFTER-HOURS SURGE PRICING (event) ────────
|
|
@@ -681,18 +692,42 @@ const config = {
|
|
|
681
692
|
|
|
682
693
|
// ── EVERYTHING HOOKS ─────────────────────────────────
|
|
683
694
|
if (type === "everything") {
|
|
684
|
-
|
|
685
|
-
|
|
695
|
+
if (!record.length) return record;
|
|
696
|
+
const profile = meta.profile;
|
|
697
|
+
|
|
698
|
+
// ── SUPER-PROP STAMPING ──────────────────────────
|
|
699
|
+
// Stamp superProps from profile so they are consistent per-user.
|
|
700
|
+
if (profile) {
|
|
701
|
+
const tier = profile.subscription_tier;
|
|
702
|
+
const plat = profile.Platform;
|
|
703
|
+
record.forEach(e => {
|
|
704
|
+
if (tier) e.subscription_tier = tier;
|
|
705
|
+
if (plat) e.Platform = plat;
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// ── HOOK 8: FREE-TIER CONVERSION DROP ────────────
|
|
710
|
+
// Free-tier users lose ~30% of "consultation completed" events
|
|
711
|
+
// (last step of Booking to Consultation funnel), simulating
|
|
712
|
+
// lower conversion for non-paying patients.
|
|
713
|
+
if (profile && profile.subscription_tier === "free") {
|
|
714
|
+
record = record.filter(e => {
|
|
715
|
+
if (e.event === "consultation completed" && chance.bool({ likelihood: 30 })) {
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
return true;
|
|
719
|
+
});
|
|
720
|
+
}
|
|
686
721
|
|
|
687
722
|
// ── HOOK 3: EXPERIENCED DOCTOR SATISFACTION ──────
|
|
688
723
|
// Users with >50 consultation events get boosted satisfaction scores.
|
|
689
724
|
let consultCount = 0;
|
|
690
|
-
|
|
725
|
+
record.forEach(e => {
|
|
691
726
|
if (e.event === "consultation completed") consultCount++;
|
|
692
727
|
});
|
|
693
728
|
|
|
694
729
|
if (consultCount > 50) {
|
|
695
|
-
|
|
730
|
+
record.forEach(e => {
|
|
696
731
|
if (e.event === "consultation completed") {
|
|
697
732
|
e.satisfaction_score = chance.floating({ min: 4.0, max: 5.0, fixed: 1 });
|
|
698
733
|
}
|
|
@@ -701,18 +736,18 @@ const config = {
|
|
|
701
736
|
|
|
702
737
|
// ── HOOK 4: VIDEO CONSULTATION FOLLOW-UP LIFT ────
|
|
703
738
|
// Patients with video consultations get 2x follow-up events.
|
|
704
|
-
const hasVideoConsult =
|
|
739
|
+
const hasVideoConsult = record.some(e =>
|
|
705
740
|
e.event === "consultation completed" && e.consultation_mode === "video"
|
|
706
741
|
);
|
|
707
742
|
if (hasVideoConsult) {
|
|
708
|
-
const templateFollowUp =
|
|
743
|
+
const templateFollowUp = record.find(e => e.event === "follow up scheduled");
|
|
709
744
|
if (templateFollowUp) {
|
|
710
|
-
const videoConsults =
|
|
745
|
+
const videoConsults = record.filter(e =>
|
|
711
746
|
e.event === "consultation completed" && e.consultation_mode === "video"
|
|
712
747
|
);
|
|
713
748
|
videoConsults.forEach(vc => {
|
|
714
749
|
if (chance.bool({ likelihood: 60 })) {
|
|
715
|
-
|
|
750
|
+
record.push({
|
|
716
751
|
...templateFollowUp,
|
|
717
752
|
time: dayjs(vc.time).add(chance.integer({ min: 1, max: 7 }), "days").toISOString(),
|
|
718
753
|
user_id: vc.user_id,
|
|
@@ -726,17 +761,17 @@ const config = {
|
|
|
726
761
|
|
|
727
762
|
// ── HOOK 5: CHRONIC CONDITION REFILL CHAIN ───────
|
|
728
763
|
// Patients with chronic prescriptions get refills every ~30 days.
|
|
729
|
-
const chronicRxs =
|
|
764
|
+
const chronicRxs = record.filter(e =>
|
|
730
765
|
e.event === "prescription issued" && e.condition_type === "chronic"
|
|
731
766
|
);
|
|
732
767
|
if (chronicRxs.length > 0) {
|
|
733
|
-
const templateRefill =
|
|
768
|
+
const templateRefill = record.find(e => e.event === "prescription refill");
|
|
734
769
|
if (templateRefill) {
|
|
735
770
|
chronicRxs.forEach(rx => {
|
|
736
771
|
const rxTime = dayjs(rx.time);
|
|
737
772
|
const refillsToAdd = chance.integer({ min: 2, max: 4 });
|
|
738
773
|
for (let i = 1; i <= refillsToAdd; i++) {
|
|
739
|
-
|
|
774
|
+
record.push({
|
|
740
775
|
...templateRefill,
|
|
741
776
|
time: rxTime.add(30 * i + chance.integer({ min: -3, max: 3 }), "days").toISOString(),
|
|
742
777
|
user_id: rx.user_id,
|
|
@@ -751,10 +786,10 @@ const config = {
|
|
|
751
786
|
|
|
752
787
|
// ── HOOK 6: OCCASIONAL PATIENT NO-SHOWS ──────────
|
|
753
788
|
// Low-activity patients (< 15 events) lose 25% of appointments.
|
|
754
|
-
if (
|
|
755
|
-
for (let i =
|
|
756
|
-
if (
|
|
757
|
-
|
|
789
|
+
if (record.length < 15) {
|
|
790
|
+
for (let i = record.length - 1; i >= 0; i--) {
|
|
791
|
+
if (record[i].event === "appointment booked" && chance.bool({ likelihood: 25 })) {
|
|
792
|
+
record.splice(i, 1);
|
|
758
793
|
}
|
|
759
794
|
}
|
|
760
795
|
}
|
|
@@ -364,52 +364,6 @@
|
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
366
|
],
|
|
367
|
-
"superProps": {
|
|
368
|
-
"platform": [
|
|
369
|
-
"web",
|
|
370
|
-
"ios",
|
|
371
|
-
"android"
|
|
372
|
-
],
|
|
373
|
-
"insurance_type": [
|
|
374
|
-
"auto",
|
|
375
|
-
"home",
|
|
376
|
-
"life",
|
|
377
|
-
"health",
|
|
378
|
-
"renters"
|
|
379
|
-
],
|
|
380
|
-
"app_version": [
|
|
381
|
-
"2.10"
|
|
382
|
-
]
|
|
383
|
-
},
|
|
384
|
-
"userProps": {
|
|
385
|
-
"age_range": [
|
|
386
|
-
"18-25",
|
|
387
|
-
"26-35",
|
|
388
|
-
"36-45",
|
|
389
|
-
"46-55",
|
|
390
|
-
"56-65",
|
|
391
|
-
"65+"
|
|
392
|
-
],
|
|
393
|
-
"risk_profile": [
|
|
394
|
-
"low",
|
|
395
|
-
"medium",
|
|
396
|
-
"high"
|
|
397
|
-
],
|
|
398
|
-
"policy_count": [
|
|
399
|
-
3
|
|
400
|
-
],
|
|
401
|
-
"lifetime_premium": {
|
|
402
|
-
"$range": [
|
|
403
|
-
8131,
|
|
404
|
-
43291
|
|
405
|
-
]
|
|
406
|
-
},
|
|
407
|
-
"preferred_contact": [
|
|
408
|
-
"email",
|
|
409
|
-
"phone",
|
|
410
|
-
"app_notification"
|
|
411
|
-
]
|
|
412
|
-
},
|
|
413
367
|
"funnels": [
|
|
414
368
|
{
|
|
415
369
|
"sequence": [
|
|
@@ -417,6 +371,7 @@
|
|
|
417
371
|
"page viewed",
|
|
418
372
|
"quote requested"
|
|
419
373
|
],
|
|
374
|
+
"name": "Onboarding",
|
|
420
375
|
"isFirstFunnel": true,
|
|
421
376
|
"conversionRate": 85,
|
|
422
377
|
"timeToConvert": 0.5
|
|
@@ -428,6 +383,7 @@
|
|
|
428
383
|
"document uploaded",
|
|
429
384
|
"application submitted"
|
|
430
385
|
],
|
|
386
|
+
"name": "Application Completion",
|
|
431
387
|
"conversionRate": 60,
|
|
432
388
|
"timeToConvert": 48,
|
|
433
389
|
"weight": 4
|
|
@@ -438,6 +394,7 @@
|
|
|
438
394
|
"application approved",
|
|
439
395
|
"policy activated"
|
|
440
396
|
],
|
|
397
|
+
"name": "Application Approval",
|
|
441
398
|
"conversionRate": 70,
|
|
442
399
|
"timeToConvert": 72,
|
|
443
400
|
"weight": 3
|
|
@@ -448,6 +405,7 @@
|
|
|
448
405
|
"claim status checked",
|
|
449
406
|
"support ticket created"
|
|
450
407
|
],
|
|
408
|
+
"name": "Claims Process",
|
|
451
409
|
"conversionRate": 50,
|
|
452
410
|
"timeToConvert": 24,
|
|
453
411
|
"weight": 2
|
|
@@ -458,9 +416,70 @@
|
|
|
458
416
|
"payment made",
|
|
459
417
|
"renewal completed"
|
|
460
418
|
],
|
|
419
|
+
"name": "Policy Renewal",
|
|
461
420
|
"conversionRate": 65,
|
|
462
421
|
"timeToConvert": 72,
|
|
463
422
|
"weight": 3
|
|
464
423
|
}
|
|
465
|
-
]
|
|
424
|
+
],
|
|
425
|
+
"superProps": {
|
|
426
|
+
"Platform": [
|
|
427
|
+
"web",
|
|
428
|
+
"ios",
|
|
429
|
+
"android"
|
|
430
|
+
],
|
|
431
|
+
"insurance_type": [
|
|
432
|
+
"auto",
|
|
433
|
+
"home",
|
|
434
|
+
"life",
|
|
435
|
+
"health",
|
|
436
|
+
"renters"
|
|
437
|
+
],
|
|
438
|
+
"app_version": [
|
|
439
|
+
"2.10"
|
|
440
|
+
]
|
|
441
|
+
},
|
|
442
|
+
"userProps": {
|
|
443
|
+
"age_range": [
|
|
444
|
+
"18-25",
|
|
445
|
+
"26-35",
|
|
446
|
+
"36-45",
|
|
447
|
+
"46-55",
|
|
448
|
+
"56-65",
|
|
449
|
+
"65+"
|
|
450
|
+
],
|
|
451
|
+
"risk_profile": [
|
|
452
|
+
"low",
|
|
453
|
+
"medium",
|
|
454
|
+
"high"
|
|
455
|
+
],
|
|
456
|
+
"policy_count": [
|
|
457
|
+
3
|
|
458
|
+
],
|
|
459
|
+
"lifetime_premium": {
|
|
460
|
+
"$range": [
|
|
461
|
+
8131,
|
|
462
|
+
43291
|
|
463
|
+
]
|
|
464
|
+
},
|
|
465
|
+
"preferred_contact": [
|
|
466
|
+
"email",
|
|
467
|
+
"phone",
|
|
468
|
+
"app_notification"
|
|
469
|
+
]
|
|
470
|
+
},
|
|
471
|
+
"scdProps": {
|
|
472
|
+
"policy_status": {
|
|
473
|
+
"type": "user",
|
|
474
|
+
"frequency": "month",
|
|
475
|
+
"timing": "fuzzy",
|
|
476
|
+
"max": 8,
|
|
477
|
+
"values": [
|
|
478
|
+
"pending",
|
|
479
|
+
"active",
|
|
480
|
+
"lapsed",
|
|
481
|
+
"renewed"
|
|
482
|
+
]
|
|
483
|
+
}
|
|
484
|
+
}
|
|
466
485
|
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
// ── TWEAK THESE ──
|
|
2
|
+
const SEED = "dm4-insurance";
|
|
3
|
+
const num_days = 100;
|
|
4
|
+
const num_users = 5_000;
|
|
5
|
+
const avg_events_per_user = 120;
|
|
6
|
+
let token = "your-mixpanel-token";
|
|
7
|
+
|
|
8
|
+
// ── env overrides ──
|
|
9
|
+
if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
|
|
10
|
+
|
|
1
11
|
import dayjs from "dayjs";
|
|
2
12
|
import utc from "dayjs/plugin/utc.js";
|
|
3
13
|
import "dotenv/config";
|
|
4
14
|
import * as u from "../../lib/utils/utils.js";
|
|
5
15
|
|
|
6
|
-
const SEED = "dm4-insurance";
|
|
7
16
|
dayjs.extend(utc);
|
|
8
17
|
const chance = u.initChance(SEED);
|
|
9
|
-
const num_users = 5_000;
|
|
10
|
-
const days = 100;
|
|
11
18
|
|
|
12
19
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
13
20
|
|
|
@@ -126,17 +133,17 @@ const days = 100;
|
|
|
126
133
|
|
|
127
134
|
// ── Time constants for hook calculations ──
|
|
128
135
|
const NOW = dayjs();
|
|
129
|
-
const DATASET_START = NOW.subtract(
|
|
136
|
+
const DATASET_START = NOW.subtract(num_days, "days");
|
|
130
137
|
const V211_DATE = DATASET_START.add(30, "days");
|
|
131
138
|
const V212_DATE = DATASET_START.add(60, "days");
|
|
132
139
|
const V213_DATE = NOW.subtract(10, "days");
|
|
133
140
|
|
|
134
141
|
/** @type {Config} */
|
|
135
142
|
const config = {
|
|
136
|
-
token
|
|
143
|
+
token,
|
|
137
144
|
seed: SEED,
|
|
138
|
-
numDays:
|
|
139
|
-
numEvents: num_users *
|
|
145
|
+
numDays: num_days,
|
|
146
|
+
numEvents: num_users * avg_events_per_user,
|
|
140
147
|
numUsers: num_users,
|
|
141
148
|
hasAnonIds: false,
|
|
142
149
|
hasSessionIds: true,
|
|
@@ -153,11 +160,17 @@ const config = {
|
|
|
153
160
|
hasAdSpend: false,
|
|
154
161
|
percentUsersBornInDataset: 35,
|
|
155
162
|
hasAvatar: true,
|
|
156
|
-
batchSize: 2_500_000,
|
|
157
163
|
concurrency: 1,
|
|
158
164
|
writeToDisk: false,
|
|
159
165
|
|
|
160
|
-
scdProps: {
|
|
166
|
+
scdProps: {
|
|
167
|
+
policy_status: {
|
|
168
|
+
values: ["pending", "active", "lapsed", "renewed"],
|
|
169
|
+
frequency: "month",
|
|
170
|
+
timing: "fuzzy",
|
|
171
|
+
max: 8
|
|
172
|
+
}
|
|
173
|
+
},
|
|
161
174
|
mirrorProps: {},
|
|
162
175
|
lookupTables: [],
|
|
163
176
|
|
|
@@ -425,13 +438,16 @@ const config = {
|
|
|
425
438
|
|
|
426
439
|
// ── Super Props (on every event) ──
|
|
427
440
|
superProps: {
|
|
428
|
-
|
|
441
|
+
Platform: ["web", "ios", "android"],
|
|
429
442
|
insurance_type: ["auto", "home", "life", "health", "renters"],
|
|
430
443
|
app_version: ["2.10"],
|
|
431
444
|
},
|
|
432
445
|
|
|
433
446
|
// ── User Props (set once per user) ──
|
|
434
447
|
userProps: {
|
|
448
|
+
Platform: ["web", "ios", "android"],
|
|
449
|
+
insurance_type: ["auto", "home", "life", "health", "renters"],
|
|
450
|
+
app_version: ["2.10"],
|
|
435
451
|
age_range: ["18-25", "26-35", "36-45", "46-55", "56-65", "65+"],
|
|
436
452
|
risk_profile: ["low", "medium", "high"],
|
|
437
453
|
policy_count: u.weighNumRange(0, 5, 0.5, 1),
|
|
@@ -471,6 +487,14 @@ const config = {
|
|
|
471
487
|
const userEvents = record;
|
|
472
488
|
if (userEvents.length === 0) return record;
|
|
473
489
|
|
|
490
|
+
// Stamp superProps from profile for consistency
|
|
491
|
+
const profile = meta.profile;
|
|
492
|
+
userEvents.forEach(e => {
|
|
493
|
+
e.Platform = profile.Platform;
|
|
494
|
+
e.insurance_type = profile.insurance_type;
|
|
495
|
+
e.app_version = profile.app_version;
|
|
496
|
+
});
|
|
497
|
+
|
|
474
498
|
// ─── Hook #1: VERSION STAMPING ───
|
|
475
499
|
// Deterministic app_version on every event based on its final timestamp.
|
|
476
500
|
// v2.10 (days 0-30) → v2.11 (30-60) → v2.12 (60-90) → v2.13 (last 10 days)
|
|
@@ -446,6 +446,9 @@
|
|
|
446
446
|
},
|
|
447
447
|
{
|
|
448
448
|
"event": "account deactivated",
|
|
449
|
+
"isChurnEvent": true,
|
|
450
|
+
"returnLikelihood": 0.1,
|
|
451
|
+
"isStrictEvent": true,
|
|
449
452
|
"properties": {
|
|
450
453
|
"reason": [
|
|
451
454
|
"too_expensive",
|
|
@@ -457,43 +460,6 @@
|
|
|
457
460
|
}
|
|
458
461
|
}
|
|
459
462
|
],
|
|
460
|
-
"superProps": {
|
|
461
|
-
"platform": [
|
|
462
|
-
"web",
|
|
463
|
-
"desktop_app"
|
|
464
|
-
],
|
|
465
|
-
"subscription_plan": [
|
|
466
|
-
"free_trial",
|
|
467
|
-
"starter",
|
|
468
|
-
"professional",
|
|
469
|
-
"enterprise"
|
|
470
|
-
]
|
|
471
|
-
},
|
|
472
|
-
"userProps": {
|
|
473
|
-
"company_tier": [
|
|
474
|
-
"small_business"
|
|
475
|
-
],
|
|
476
|
-
"warehouse_count": [
|
|
477
|
-
8,
|
|
478
|
-
3
|
|
479
|
-
],
|
|
480
|
-
"employee_count": {
|
|
481
|
-
"$range": [
|
|
482
|
-
94,
|
|
483
|
-
385
|
|
484
|
-
]
|
|
485
|
-
},
|
|
486
|
-
"industry": [
|
|
487
|
-
"retail",
|
|
488
|
-
"manufacturing",
|
|
489
|
-
"food_beverage",
|
|
490
|
-
"pharma",
|
|
491
|
-
"electronics"
|
|
492
|
-
],
|
|
493
|
-
"integration_count": [
|
|
494
|
-
0
|
|
495
|
-
]
|
|
496
|
-
},
|
|
497
463
|
"funnels": [
|
|
498
464
|
{
|
|
499
465
|
"sequence": [
|
|
@@ -502,6 +468,7 @@
|
|
|
502
468
|
"integration connected",
|
|
503
469
|
"report generated"
|
|
504
470
|
],
|
|
471
|
+
"name": "Onboarding",
|
|
505
472
|
"isFirstFunnel": true,
|
|
506
473
|
"conversionRate": 40,
|
|
507
474
|
"timeToConvert": 72,
|
|
@@ -514,6 +481,7 @@
|
|
|
514
481
|
"order received",
|
|
515
482
|
"shipment tracked"
|
|
516
483
|
],
|
|
484
|
+
"name": "Order Fulfillment",
|
|
517
485
|
"conversionRate": 35,
|
|
518
486
|
"timeToConvert": 168,
|
|
519
487
|
"weight": 5
|
|
@@ -524,6 +492,7 @@
|
|
|
524
492
|
"purchase order created",
|
|
525
493
|
"invoice processed"
|
|
526
494
|
],
|
|
495
|
+
"name": "Supplier Management",
|
|
527
496
|
"conversionRate": 45,
|
|
528
497
|
"timeToConvert": 336,
|
|
529
498
|
"weight": 3
|
|
@@ -534,6 +503,7 @@
|
|
|
534
503
|
"report generated",
|
|
535
504
|
"alert configured"
|
|
536
505
|
],
|
|
506
|
+
"name": "Integration Setup",
|
|
537
507
|
"conversionRate": 30,
|
|
538
508
|
"timeToConvert": 48,
|
|
539
509
|
"weight": 2
|
|
@@ -544,9 +514,61 @@
|
|
|
544
514
|
"purchase order created",
|
|
545
515
|
"order received"
|
|
546
516
|
],
|
|
517
|
+
"name": "Alert Response",
|
|
547
518
|
"conversionRate": 50,
|
|
548
519
|
"timeToConvert": 120,
|
|
549
520
|
"weight": 3
|
|
550
521
|
}
|
|
551
|
-
]
|
|
522
|
+
],
|
|
523
|
+
"superProps": {
|
|
524
|
+
"Platform": [
|
|
525
|
+
"web",
|
|
526
|
+
"desktop_app"
|
|
527
|
+
],
|
|
528
|
+
"subscription_plan": [
|
|
529
|
+
"free_trial",
|
|
530
|
+
"starter",
|
|
531
|
+
"professional",
|
|
532
|
+
"enterprise"
|
|
533
|
+
]
|
|
534
|
+
},
|
|
535
|
+
"userProps": {
|
|
536
|
+
"company_tier": [
|
|
537
|
+
"small_business"
|
|
538
|
+
],
|
|
539
|
+
"warehouse_count": [
|
|
540
|
+
8,
|
|
541
|
+
3
|
|
542
|
+
],
|
|
543
|
+
"employee_count": {
|
|
544
|
+
"$range": [
|
|
545
|
+
94,
|
|
546
|
+
385
|
|
547
|
+
]
|
|
548
|
+
},
|
|
549
|
+
"industry": [
|
|
550
|
+
"retail",
|
|
551
|
+
"manufacturing",
|
|
552
|
+
"food_beverage",
|
|
553
|
+
"pharma",
|
|
554
|
+
"electronics"
|
|
555
|
+
],
|
|
556
|
+
"integration_count": [
|
|
557
|
+
0
|
|
558
|
+
]
|
|
559
|
+
},
|
|
560
|
+
"scdProps": {
|
|
561
|
+
"company_tier": {
|
|
562
|
+
"type": "user",
|
|
563
|
+
"frequency": "month",
|
|
564
|
+
"timing": "fuzzy",
|
|
565
|
+
"max": 6,
|
|
566
|
+
"values": [
|
|
567
|
+
"trial",
|
|
568
|
+
"starter",
|
|
569
|
+
"professional",
|
|
570
|
+
"enterprise"
|
|
571
|
+
]
|
|
572
|
+
}
|
|
573
|
+
}
|
|
552
574
|
}
|