@ak--47/dungeon-master 1.2.0 → 1.2.2
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.js +15 -4
- package/dungeons/technical/anonymous-users.js +15 -5
- package/dungeons/technical/array-of-object-lookup.js +21 -5
- package/dungeons/technical/experiments.js +23 -5
- package/dungeons/technical/foobar.js +28 -12
- package/dungeons/technical/group-analytics.js +15 -5
- package/dungeons/technical/mirror-strategies.js +15 -5
- package/dungeons/technical/nested-objects.js +15 -4
- package/dungeons/technical/retention-cadence.js +13 -7
- package/dungeons/technical/sanity.js +25 -6
- package/dungeons/technical/scale-test.js +15 -4
- package/dungeons/technical/scd.js +24 -5
- package/dungeons/technical/simple.js +20 -5
- package/dungeons/technical/simplest.js +23 -6
- package/dungeons/technical/text-generation.js +22 -6
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/community-schema.json +1 -1
- package/dungeons/vertical/community.js +49 -28
- package/dungeons/vertical/devtools-schema.json +1 -1
- package/dungeons/vertical/devtools.js +50 -29
- package/dungeons/vertical/ecommerce.js +23 -6
- package/dungeons/vertical/education-schema.json +1 -1
- package/dungeons/vertical/education.js +29 -19
- package/dungeons/vertical/fintech-schema.json +1 -1
- package/dungeons/vertical/fintech.js +27 -8
- package/dungeons/vertical/fitness-schema.json +1 -1
- package/dungeons/vertical/fitness.js +48 -22
- package/dungeons/vertical/food-delivery-schema.json +1 -1
- package/dungeons/vertical/food-delivery.js +51 -12
- package/dungeons/vertical/gaming-schema.json +1 -1
- package/dungeons/vertical/gaming.js +41 -7
- package/dungeons/vertical/healthcare-schema.json +1 -1
- package/dungeons/vertical/healthcare.js +69 -40
- package/dungeons/vertical/insurance-application-schema.json +1 -1
- package/dungeons/vertical/insurance-application.js +26 -8
- package/dungeons/vertical/logistics-schema.json +1 -1
- package/dungeons/vertical/logistics.js +69 -41
- package/dungeons/vertical/marketplace-schema.json +1 -1
- package/dungeons/vertical/marketplace.js +44 -19
- package/dungeons/vertical/media.js +56 -15
- package/dungeons/vertical/rpg-schema.json +1 -1
- package/dungeons/vertical/rpg.js +38 -8
- package/dungeons/vertical/sass.js +24 -7
- package/dungeons/vertical/social.js +23 -7
- package/dungeons/vertical/travel-schema.json +1 -1
- package/dungeons/vertical/travel.js +50 -19
- package/index.js +3 -0
- package/lib/core/config-validator.js +13 -2
- package/lib/generators/events.js +3 -5
- package/lib/orchestrators/mixpanel-sender.js +64 -3
- package/lib/orchestrators/user-loop.js +20 -0
- package/lib/utils/utils.js +129 -9
- package/package.json +2 -2
- package/types.d.ts +5 -3
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
// ── TWEAK THESE ──
|
|
2
|
+
const SEED = "SUPER DUPER DANGEROUS BROOO";
|
|
3
|
+
const num_days = 92;
|
|
4
|
+
const num_users = 8_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 { weighNumRange, range, date, initChance, exhaust, choose, integer } from "../../lib/utils/utils.js";
|
|
5
15
|
import { createTextGenerator, generateBatch } from "../../lib/generators/text.js";
|
|
6
16
|
|
|
7
|
-
const SEED = "SUPER DUPER DANGEROUS BROOO";
|
|
8
17
|
dayjs.extend(utc);
|
|
9
18
|
const chance = initChance(SEED);
|
|
10
|
-
const num_users = 8_000;
|
|
11
|
-
const days = 92;
|
|
12
19
|
|
|
13
20
|
/** @typedef {import("../../types").Dungeon} Dungeon */
|
|
14
21
|
|
|
@@ -398,9 +405,9 @@ const webinarChatGen = createTextGenerator({
|
|
|
398
405
|
/** @type {Dungeon} */
|
|
399
406
|
const dungeon = {
|
|
400
407
|
seed: SEED,
|
|
401
|
-
token
|
|
402
|
-
numDays:
|
|
403
|
-
numEvents: num_users *
|
|
408
|
+
token,
|
|
409
|
+
numDays: num_days,
|
|
410
|
+
numEvents: num_users * avg_events_per_user,
|
|
404
411
|
numUsers: num_users,
|
|
405
412
|
hasAnonIds: false,
|
|
406
413
|
hasSessionIds: true, // Enable for chat flow tracking
|
|
@@ -770,6 +777,8 @@ const dungeon = {
|
|
|
770
777
|
preferred_communication: ["email", "chat", "phone", "self_service"],
|
|
771
778
|
is_power_user: [false],
|
|
772
779
|
risk_level: ["healthy"],
|
|
780
|
+
product_tier: ["free", "basic", "pro", "enterprise"],
|
|
781
|
+
data_center: ["us-east", "us-west", "eu-central", "asia-pacific"],
|
|
773
782
|
},
|
|
774
783
|
|
|
775
784
|
// ============= Slowly Changing Dimensions =============
|
|
@@ -804,6 +813,13 @@ const dungeon = {
|
|
|
804
813
|
|
|
805
814
|
// --- everything hook: enterprise users get a satisfaction survey event ---
|
|
806
815
|
if (type === "everything" && meta && meta.profile) {
|
|
816
|
+
// stamp superProps from profile for consistency (skip feature_flags — intentionally per-event)
|
|
817
|
+
const profile = meta.profile;
|
|
818
|
+
record.forEach(e => {
|
|
819
|
+
e.product_tier = profile.product_tier;
|
|
820
|
+
e.data_center = profile.data_center;
|
|
821
|
+
});
|
|
822
|
+
|
|
807
823
|
if (meta.profile.user_tier === "enterprise" && record.length > 5) {
|
|
808
824
|
const lastEvent = record[record.length - 1];
|
|
809
825
|
record.push({
|
|
File without changes
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
// ── TWEAK THESE ──
|
|
2
|
+
const SEED = "dm4-community";
|
|
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-community";
|
|
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
|
|
|
@@ -213,12 +220,12 @@ const communityIds = v.range(1, 30).map(() => `COMM_${v.uid(4)}`);
|
|
|
213
220
|
* articles and high community reputation scores.
|
|
214
221
|
*
|
|
215
222
|
* -------------------------------------------------------------------
|
|
216
|
-
* 8. PRO SUBSCRIBER CONTENT CREATION LIFT (
|
|
223
|
+
* 8. PRO SUBSCRIBER CONTENT CREATION LIFT (everything hook)
|
|
217
224
|
* -------------------------------------------------------------------
|
|
218
225
|
*
|
|
219
|
-
* PATTERN:
|
|
220
|
-
*
|
|
221
|
-
*
|
|
226
|
+
* PATTERN: Free-tier users drop 35% of final funnel step events
|
|
227
|
+
* ("comment posted"), creating a visible conversion gap between
|
|
228
|
+
* paid and free users. Pro/supporter users keep all their events.
|
|
222
229
|
*
|
|
223
230
|
* HOW TO FIND IT IN MIXPANEL:
|
|
224
231
|
*
|
|
@@ -244,15 +251,15 @@ const communityIds = v.range(1, 30).map(() => `COMM_${v.uid(4)}`);
|
|
|
244
251
|
* Edit War Detection | edit_quality | 3.0 | 1.5 | 0.5x
|
|
245
252
|
* Lurker Churn | events after day 10 | 100% | 40% | 0.4x
|
|
246
253
|
* Creator Profiles | reputation_score | 25 | 90 | 3.6x
|
|
247
|
-
* Pro Content Creation Lift | funnel conversion | 35% | 52% | 1.5x
|
|
254
|
+
* Pro Content Creation Lift | funnel conversion | 35% | 52% | ~1.5x
|
|
248
255
|
*/
|
|
249
256
|
|
|
250
257
|
/** @type {Config} */
|
|
251
258
|
const config = {
|
|
252
|
-
token
|
|
259
|
+
token,
|
|
253
260
|
seed: SEED,
|
|
254
|
-
numDays:
|
|
255
|
-
numEvents: num_users *
|
|
261
|
+
numDays: num_days,
|
|
262
|
+
numEvents: num_users * avg_events_per_user,
|
|
256
263
|
numUsers: num_users,
|
|
257
264
|
hasAnonIds: false,
|
|
258
265
|
hasSessionIds: true,
|
|
@@ -504,7 +511,7 @@ const config = {
|
|
|
504
511
|
// -- SuperProps ----------------------------------------------------
|
|
505
512
|
superProps: {
|
|
506
513
|
subscription_tier: ["free", "free", "free", "free", "supporter", "pro"],
|
|
507
|
-
|
|
514
|
+
Platform: ["ios", "android", "web"],
|
|
508
515
|
content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
|
|
509
516
|
},
|
|
510
517
|
|
|
@@ -515,6 +522,9 @@ const config = {
|
|
|
515
522
|
articles_created: [0],
|
|
516
523
|
reputation_score: u.weighNumRange(0, 100, 0.3, 25),
|
|
517
524
|
preferred_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
|
|
525
|
+
subscription_tier: ["free", "free", "free", "free", "supporter", "pro"],
|
|
526
|
+
Platform: ["ios", "android", "web"],
|
|
527
|
+
content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
|
|
518
528
|
},
|
|
519
529
|
|
|
520
530
|
// -- Phase 2: Personas --------------------------------------------
|
|
@@ -685,18 +695,9 @@ const config = {
|
|
|
685
695
|
}
|
|
686
696
|
}
|
|
687
697
|
|
|
688
|
-
// -- HOOK 8: PRO SUBSCRIBER CONTENT CREATION LIFT
|
|
689
|
-
//
|
|
690
|
-
|
|
691
|
-
if (meta && meta.profile) {
|
|
692
|
-
const tier = meta.profile.subscription_tier;
|
|
693
|
-
if (tier === "pro") {
|
|
694
|
-
record.conversionRate = Math.min(record.conversionRate * 1.5, 95);
|
|
695
|
-
} else if (tier === "supporter") {
|
|
696
|
-
record.conversionRate = Math.min(record.conversionRate * 1.2, 90);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
698
|
+
// -- HOOK 8: PRO SUBSCRIBER CONTENT CREATION LIFT
|
|
699
|
+
// Conversion differences handled in everything hook via event filtering.
|
|
700
|
+
// (funnel-pre conversionRate modifications are diluted by organic events)
|
|
700
701
|
|
|
701
702
|
// -- HOOK 1: WEEKEND CONTENT SURGE (event) --------------------
|
|
702
703
|
// Articles published on weekends get 1.5x word_count.
|
|
@@ -725,8 +726,28 @@ const config = {
|
|
|
725
726
|
|
|
726
727
|
// -- EVERYTHING HOOKS -----------------------------------------
|
|
727
728
|
if (type === "everything") {
|
|
728
|
-
|
|
729
|
+
let events = record;
|
|
729
730
|
if (!events.length) return record;
|
|
731
|
+
const profile = meta && meta.profile ? meta.profile : {};
|
|
732
|
+
|
|
733
|
+
// -- SUPERPROP STAMPING -----------------------------------
|
|
734
|
+
// Stamp superProp values from profile onto every event so
|
|
735
|
+
// they stay consistent per-user instead of randomizing per-event.
|
|
736
|
+
events.forEach(e => {
|
|
737
|
+
if (profile.subscription_tier) e.subscription_tier = profile.subscription_tier;
|
|
738
|
+
if (profile.Platform) e.Platform = profile.Platform;
|
|
739
|
+
if (profile.content_hub) e.content_hub = profile.content_hub;
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
// -- HOOK 8: PRO SUBSCRIBER CONTENT CREATION LIFT ---------
|
|
743
|
+
// Free-tier users drop 35% of final funnel step events to
|
|
744
|
+
// create visible conversion gap vs paid subscribers.
|
|
745
|
+
if (profile.subscription_tier !== "pro" && profile.subscription_tier !== "supporter") {
|
|
746
|
+
events = events.filter(e => {
|
|
747
|
+
if (e.event === "comment posted" && chance.bool({ likelihood: 35 })) return false;
|
|
748
|
+
return true;
|
|
749
|
+
});
|
|
750
|
+
}
|
|
730
751
|
|
|
731
752
|
// -- HOOK 3: POWER CREATOR ENGAGEMENT LIFT ----------------
|
|
732
753
|
// Users with >20 article_published events get 3x upvote_count.
|
|
@@ -745,7 +766,7 @@ const config = {
|
|
|
745
766
|
|
|
746
767
|
// -- HOOK 4: DISCUSSION DEPTH BY CONTRIBUTOR TYPE ---------
|
|
747
768
|
// Active contributors get cloned comment_posted events.
|
|
748
|
-
if (
|
|
769
|
+
if (profile.segment === "active_contributor") {
|
|
749
770
|
const templateComment = events.find(e => e.event === "comment posted");
|
|
750
771
|
if (templateComment) {
|
|
751
772
|
const existingComments = events.filter(e => e.event === "comment posted");
|
|
@@ -784,7 +805,7 @@ const config = {
|
|
|
784
805
|
}
|
|
785
806
|
}
|
|
786
807
|
|
|
787
|
-
return
|
|
808
|
+
return events;
|
|
788
809
|
}
|
|
789
810
|
|
|
790
811
|
return record;
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
// ── TWEAK THESE ──
|
|
2
|
+
const SEED = "dm4-devtools";
|
|
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-devtools";
|
|
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
|
|
|
@@ -208,12 +215,13 @@ const repoIds = v.range(1, 150).map(() => `REPO_${v.uid(6)}`);
|
|
|
208
215
|
* manage organization-wide CI/CD infrastructure.
|
|
209
216
|
*
|
|
210
217
|
* ---------------------------------------------------------------
|
|
211
|
-
* 8. ENTERPRISE BUILD-DEPLOY FUNNEL LIFT (
|
|
218
|
+
* 8. ENTERPRISE BUILD-DEPLOY FUNNEL LIFT (everything hook)
|
|
212
219
|
* ---------------------------------------------------------------
|
|
213
220
|
*
|
|
214
|
-
* PATTERN:
|
|
215
|
-
*
|
|
216
|
-
*
|
|
221
|
+
* PATTERN: Free-tier users drop 35% of final funnel step events
|
|
222
|
+
* ("monitoring dashboard viewed"), creating a visible conversion
|
|
223
|
+
* gap between paid and free users. Enterprise/business users keep
|
|
224
|
+
* all their events.
|
|
217
225
|
*
|
|
218
226
|
* HOW TO FIND IT IN MIXPANEL:
|
|
219
227
|
*
|
|
@@ -240,15 +248,15 @@ const repoIds = v.range(1, 150).map(() => `REPO_${v.uid(6)}`);
|
|
|
240
248
|
* OSS -> Paid Conversion | team tier events | 0% | 30% | -
|
|
241
249
|
* Post-Outage Recovery | deploys/day | 50 | 100+ | 2x+
|
|
242
250
|
* DevOps Lead Profiles | team_size | 10 | 30 | 3x
|
|
243
|
-
* Enterprise Funnel Lift | funnel conversion | 40% | 60% | 1.5x
|
|
251
|
+
* Enterprise Funnel Lift | funnel conversion | 40% | 60% | ~1.5x
|
|
244
252
|
*/
|
|
245
253
|
|
|
246
254
|
/** @type {Config} */
|
|
247
255
|
const config = {
|
|
248
|
-
token
|
|
256
|
+
token,
|
|
249
257
|
seed: SEED,
|
|
250
|
-
numDays:
|
|
251
|
-
numEvents: num_users *
|
|
258
|
+
numDays: num_days,
|
|
259
|
+
numEvents: num_users * avg_events_per_user,
|
|
252
260
|
numUsers: num_users,
|
|
253
261
|
hasAnonIds: false,
|
|
254
262
|
hasSessionIds: true,
|
|
@@ -507,7 +515,7 @@ const config = {
|
|
|
507
515
|
// -- SuperProps --------------------------------------------
|
|
508
516
|
superProps: {
|
|
509
517
|
subscription_tier: ["free", "free", "free", "team", "team", "business", "enterprise"],
|
|
510
|
-
|
|
518
|
+
Platform: ["web", "web", "desktop_app", "cli"],
|
|
511
519
|
language: ["javascript", "python", "go", "rust", "java", "typescript"],
|
|
512
520
|
},
|
|
513
521
|
|
|
@@ -519,6 +527,9 @@ const config = {
|
|
|
519
527
|
repos_connected: [0],
|
|
520
528
|
org_name: ["personal"],
|
|
521
529
|
experience_level: ["junior", "junior", "mid", "mid", "mid", "senior", "senior"],
|
|
530
|
+
subscription_tier: ["free", "free", "free", "team", "team", "business", "enterprise"],
|
|
531
|
+
Platform: ["web", "web", "desktop_app", "cli"],
|
|
532
|
+
language: ["javascript", "python", "go", "rust", "java", "typescript"],
|
|
522
533
|
},
|
|
523
534
|
|
|
524
535
|
// -- Phase 2: Personas ------------------------------------
|
|
@@ -726,19 +737,9 @@ const config = {
|
|
|
726
737
|
}
|
|
727
738
|
}
|
|
728
739
|
|
|
729
|
-
// -- HOOK 8: ENTERPRISE BUILD-DEPLOY FUNNEL LIFT
|
|
730
|
-
//
|
|
731
|
-
//
|
|
732
|
-
if (type === "funnel-pre") {
|
|
733
|
-
if (meta && meta.profile) {
|
|
734
|
-
const tier = meta.profile.subscription_tier;
|
|
735
|
-
if (tier === "enterprise") {
|
|
736
|
-
record.conversionRate = Math.min(record.conversionRate * 1.5, 95);
|
|
737
|
-
} else if (tier === "business") {
|
|
738
|
-
record.conversionRate = Math.min(record.conversionRate * 1.2, 90);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
}
|
|
740
|
+
// -- HOOK 8: ENTERPRISE BUILD-DEPLOY FUNNEL LIFT
|
|
741
|
+
// Conversion differences handled in everything hook via event filtering.
|
|
742
|
+
// (funnel-pre conversionRate modifications are diluted by organic events)
|
|
742
743
|
|
|
743
744
|
// -- HOOK 1: BUILD FAILURE CASCADE (event) ----------------
|
|
744
745
|
// Failed builds get 2x duration (retries take longer).
|
|
@@ -762,8 +763,28 @@ const config = {
|
|
|
762
763
|
|
|
763
764
|
// -- EVERYTHING HOOKS -------------------------------------
|
|
764
765
|
if (type === "everything") {
|
|
765
|
-
|
|
766
|
+
let events = record;
|
|
766
767
|
if (!events.length) return record;
|
|
768
|
+
const profile = meta && meta.profile ? meta.profile : {};
|
|
769
|
+
|
|
770
|
+
// -- SUPERPROP STAMPING -------------------------------
|
|
771
|
+
// Stamp superProp values from profile onto every event so
|
|
772
|
+
// they stay consistent per-user instead of randomizing per-event.
|
|
773
|
+
events.forEach(e => {
|
|
774
|
+
if (profile.subscription_tier) e.subscription_tier = profile.subscription_tier;
|
|
775
|
+
if (profile.Platform) e.Platform = profile.Platform;
|
|
776
|
+
if (profile.language) e.language = profile.language;
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// -- HOOK 8: ENTERPRISE BUILD-DEPLOY FUNNEL LIFT ------
|
|
780
|
+
// Free-tier users drop 35% of final funnel step events to
|
|
781
|
+
// create visible conversion gap vs paid subscribers.
|
|
782
|
+
if (profile.subscription_tier !== "enterprise" && profile.subscription_tier !== "business") {
|
|
783
|
+
events = events.filter(e => {
|
|
784
|
+
if (e.event === "monitoring dashboard viewed" && chance.bool({ likelihood: 35 })) return false;
|
|
785
|
+
return true;
|
|
786
|
+
});
|
|
787
|
+
}
|
|
767
788
|
|
|
768
789
|
// -- HOOK 3: COPILOT PR VELOCITY ----------------------
|
|
769
790
|
// Users with ai_assist="copilot" on any PR get 1.5x more PRs.
|
|
@@ -805,7 +826,7 @@ const config = {
|
|
|
805
826
|
|
|
806
827
|
// -- HOOK 5: OPEN SOURCE -> PAID CONVERSION -----------
|
|
807
828
|
// OSS users with >15 events get 30% of events converted to "team" tier.
|
|
808
|
-
if (
|
|
829
|
+
if (profile.segment === "oss_user" && events.length > 15) {
|
|
809
830
|
const conversionPoint = Math.floor(events.length * 0.7);
|
|
810
831
|
for (let i = conversionPoint; i < events.length; i++) {
|
|
811
832
|
events[i].subscription_tier = "team";
|
|
@@ -833,7 +854,7 @@ const config = {
|
|
|
833
854
|
}
|
|
834
855
|
});
|
|
835
856
|
|
|
836
|
-
return
|
|
857
|
+
return events;
|
|
837
858
|
}
|
|
838
859
|
|
|
839
860
|
return record;
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
// ── TWEAK THESE ──
|
|
2
|
+
const SEED = "simple is best";
|
|
3
|
+
const num_days = 108;
|
|
4
|
+
const num_users = 50_000;
|
|
5
|
+
const avg_events_per_user = 40;
|
|
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 Chance from 'chance';
|
|
2
12
|
let chance = new Chance();
|
|
3
13
|
import dayjs from "dayjs";
|
|
@@ -130,11 +140,11 @@ function makeProducts(maxItems = 5) {
|
|
|
130
140
|
|
|
131
141
|
/** @type {import('../types.d.ts').Dungeon} */
|
|
132
142
|
const config = {
|
|
133
|
-
token
|
|
134
|
-
seed:
|
|
135
|
-
numDays:
|
|
136
|
-
numEvents:
|
|
137
|
-
numUsers:
|
|
143
|
+
token,
|
|
144
|
+
seed: SEED,
|
|
145
|
+
numDays: num_days,
|
|
146
|
+
numEvents: num_users * avg_events_per_user,
|
|
147
|
+
numUsers: num_users,
|
|
138
148
|
format: 'json', //csv or json
|
|
139
149
|
region: "US",
|
|
140
150
|
hasAnonIds: false, //if true, anonymousIds are created for each user
|
|
@@ -305,7 +315,8 @@ const config = {
|
|
|
305
315
|
userProps: {
|
|
306
316
|
title: chance.profession.bind(chance),
|
|
307
317
|
luckyNumber: weighNumRange(42, 420, .3),
|
|
308
|
-
spiritAnimal: spiritAnimals
|
|
318
|
+
spiritAnimal: spiritAnimals,
|
|
319
|
+
theme: ["light", "dark", "custom", "light", "dark"],
|
|
309
320
|
},
|
|
310
321
|
scdProps: {
|
|
311
322
|
loyalty_tier: {
|
|
@@ -418,6 +429,12 @@ const config = {
|
|
|
418
429
|
|
|
419
430
|
|
|
420
431
|
if (type === "everything") {
|
|
432
|
+
// Stamp superProps from profile for consistency
|
|
433
|
+
const profile = meta.profile;
|
|
434
|
+
record.forEach(e => {
|
|
435
|
+
e.theme = profile.theme;
|
|
436
|
+
});
|
|
437
|
+
|
|
421
438
|
// big themes!
|
|
422
439
|
|
|
423
440
|
// users who view items in the home and garden category churn more frequently
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
+
// ── TWEAK THESE ──
|
|
2
|
+
const SEED = "harness-education";
|
|
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 = "harness-education";
|
|
8
17
|
dayjs.extend(utc);
|
|
9
18
|
const chance = u.initChance(SEED);
|
|
10
|
-
const num_users = 5_000;
|
|
11
|
-
const days = 100;
|
|
12
19
|
|
|
13
20
|
/** @typedef {import("../../types").Dungeon} Config */
|
|
14
21
|
|
|
@@ -183,10 +190,10 @@ const problemIds = v.range(1, 601).map(n => `problem_${v.uid(6)}`);
|
|
|
183
190
|
|
|
184
191
|
/** @type {Config} */
|
|
185
192
|
const config = {
|
|
186
|
-
token
|
|
193
|
+
token,
|
|
187
194
|
seed: SEED,
|
|
188
|
-
numDays:
|
|
189
|
-
numEvents: num_users *
|
|
195
|
+
numDays: num_days,
|
|
196
|
+
numEvents: num_users * avg_events_per_user,
|
|
190
197
|
numUsers: num_users,
|
|
191
198
|
hasAnonIds: false,
|
|
192
199
|
hasSessionIds: true,
|
|
@@ -436,7 +443,7 @@ const config = {
|
|
|
436
443
|
],
|
|
437
444
|
|
|
438
445
|
superProps: {
|
|
439
|
-
|
|
446
|
+
Platform: ["Web", "iOS", "Android", "iPad"],
|
|
440
447
|
},
|
|
441
448
|
|
|
442
449
|
scdProps: {
|
|
@@ -466,6 +473,7 @@ const config = {
|
|
|
466
473
|
"instructor_rating": [0],
|
|
467
474
|
"learning_goal": ["none"],
|
|
468
475
|
"study_hours_per_week": [0],
|
|
476
|
+
"Platform": ["Web", "iOS", "Android", "iPad"],
|
|
469
477
|
},
|
|
470
478
|
|
|
471
479
|
groupKeys: [
|
|
@@ -505,7 +513,7 @@ const config = {
|
|
|
505
513
|
*/
|
|
506
514
|
hook: function (record, type, meta) {
|
|
507
515
|
const NOW = dayjs();
|
|
508
|
-
const DATASET_START = NOW.subtract(
|
|
516
|
+
const DATASET_START = NOW.subtract(num_days, 'days');
|
|
509
517
|
|
|
510
518
|
// ═══════════════════════════════════════════════════════════════════
|
|
511
519
|
// Hook #1: STUDENT VS INSTRUCTOR PROFILES
|
|
@@ -621,8 +629,16 @@ const config = {
|
|
|
621
629
|
// ═══════════════════════════════════════════════════════════════════
|
|
622
630
|
if (type === "everything") {
|
|
623
631
|
const userEvents = record;
|
|
632
|
+
const profile = meta.profile;
|
|
624
633
|
const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
|
|
625
634
|
|
|
635
|
+
// ── Stamp superProps from profile so they stay consistent per user ──
|
|
636
|
+
if (profile) {
|
|
637
|
+
userEvents.forEach((event) => {
|
|
638
|
+
if (profile.Platform !== undefined) event.Platform = profile.Platform;
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
626
642
|
// ---------------------------------------------------------------
|
|
627
643
|
// First pass: identify user patterns
|
|
628
644
|
// ---------------------------------------------------------------
|
|
@@ -720,7 +736,7 @@ const config = {
|
|
|
720
736
|
}
|
|
721
737
|
|
|
722
738
|
// Hook #7: FREE VS PAID - reinforce the subscription effect on certificates
|
|
723
|
-
const subStatus =
|
|
739
|
+
const subStatus = profile ? profile.subscription_status : "free";
|
|
724
740
|
if (subStatus === "free") {
|
|
725
741
|
// Free users lose 55% of their certificates (simulating lower completion)
|
|
726
742
|
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
@@ -778,17 +794,11 @@ const config = {
|
|
|
778
794
|
// Hook #7: FREE VS PAID COURSES (funnel-pre)
|
|
779
795
|
// ═══════════════════════════════════════════════════════════════════
|
|
780
796
|
if (type === "funnel-pre") {
|
|
781
|
-
//
|
|
797
|
+
// Hook #7: FREE VS PAID — prop tagging only
|
|
798
|
+
// conversionRate manipulation removed; the everything hook handles
|
|
799
|
+
// the completion gap by dropping 55% of free-user certificates
|
|
782
800
|
if (meta && meta.profile && meta.funnel) {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
if (subscriptionStatus === "free") {
|
|
786
|
-
// Free users convert at 0.5x rate
|
|
787
|
-
record.conversionRate = (record.conversionRate || 0.25) * 0.5;
|
|
788
|
-
} else if (subscriptionStatus === "monthly" || subscriptionStatus === "annual") {
|
|
789
|
-
// Paid subscribers convert at 1.5x rate
|
|
790
|
-
record.conversionRate = (record.conversionRate || 0.25) * 1.5;
|
|
791
|
-
}
|
|
801
|
+
// no-op: kept for future prop-setting if needed
|
|
792
802
|
}
|
|
793
803
|
}
|
|
794
804
|
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
+
// ── TWEAK THESE ──
|
|
2
|
+
const SEED = "harness-fintech";
|
|
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 = "harness-fintech";
|
|
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
|
|
|
@@ -129,10 +136,10 @@ const days = 100;
|
|
|
129
136
|
|
|
130
137
|
/** @type {Config} */
|
|
131
138
|
const config = {
|
|
132
|
-
token
|
|
139
|
+
token,
|
|
133
140
|
seed: SEED,
|
|
134
|
-
numDays:
|
|
135
|
-
numEvents: num_users *
|
|
141
|
+
numDays: num_days,
|
|
142
|
+
numEvents: num_users * avg_events_per_user,
|
|
136
143
|
numUsers: num_users,
|
|
137
144
|
hasAnonIds: false,
|
|
138
145
|
hasSessionIds: true,
|
|
@@ -400,11 +407,14 @@ const config = {
|
|
|
400
407
|
|
|
401
408
|
superProps: {
|
|
402
409
|
account_tier: ["basic", "basic", "basic", "plus", "plus", "premium"],
|
|
403
|
-
|
|
410
|
+
Platform: ["ios", "android", "web"],
|
|
404
411
|
low_balance_churn: [false],
|
|
405
412
|
},
|
|
406
413
|
|
|
407
414
|
userProps: {
|
|
415
|
+
account_tier: ["basic", "basic", "basic", "plus", "plus", "premium"],
|
|
416
|
+
Platform: ["ios", "android", "web"],
|
|
417
|
+
low_balance_churn: [false],
|
|
408
418
|
"credit_score_range": ["300-579", "580-669", "670-739", "740-799", "800-850"],
|
|
409
419
|
"income_bracket": ["under_30k", "30k_50k", "50k_75k", "75k_100k", "100k_150k", "over_150k"],
|
|
410
420
|
"account_age_months": u.weighNumRange(1, 60, 0.5, 12),
|
|
@@ -449,7 +459,7 @@ const config = {
|
|
|
449
459
|
*/
|
|
450
460
|
hook: function (record, type, meta) {
|
|
451
461
|
const NOW = dayjs();
|
|
452
|
-
const DATASET_START = NOW.subtract(
|
|
462
|
+
const DATASET_START = NOW.subtract(num_days, "days");
|
|
453
463
|
|
|
454
464
|
// ===============================================================
|
|
455
465
|
// Hook #1: PERSONAL VS BUSINESS ACCOUNTS (user)
|
|
@@ -581,6 +591,15 @@ const config = {
|
|
|
581
591
|
// ===============================================================
|
|
582
592
|
if (type === "everything") {
|
|
583
593
|
const userEvents = record;
|
|
594
|
+
|
|
595
|
+
// Stamp superProps from profile for consistency
|
|
596
|
+
const profile = meta.profile;
|
|
597
|
+
userEvents.forEach(e => {
|
|
598
|
+
e.account_tier = profile.account_tier;
|
|
599
|
+
e.Platform = profile.Platform;
|
|
600
|
+
e.low_balance_churn = profile.low_balance_churn;
|
|
601
|
+
});
|
|
602
|
+
|
|
584
603
|
const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
|
|
585
604
|
|
|
586
605
|
// -----------------------------------------------------------
|