@ak--47/dungeon-master 1.5.0 → 1.5.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 +43 -0
- package/HOOKS.md +13 -0
- package/dungeons/technical/ad-spend.js +41 -49
- package/dungeons/technical/anonymous-users.js +38 -36
- package/dungeons/technical/array-of-object-lookup.js +136 -153
- package/dungeons/technical/datagen-v15-verify.js +24 -11
- package/dungeons/technical/experiments.js +42 -40
- package/dungeons/technical/foobar.js +114 -118
- package/dungeons/technical/group-analytics.js +42 -40
- package/dungeons/technical/hook-helpers-verify.js +69 -50
- package/dungeons/technical/identity-model-verify.js +22 -12
- package/dungeons/technical/mirror-strategies.js +37 -39
- package/dungeons/technical/nested-objects.js +119 -118
- package/dungeons/technical/pattern-aggregate-by-bin.js +21 -8
- package/dungeons/technical/pattern-attributed-by-source.js +23 -9
- package/dungeons/technical/pattern-frequency-by-frequency.js +21 -8
- package/dungeons/technical/pattern-funnel-frequency.js +30 -15
- package/dungeons/technical/pattern-ttc-by-segment.js +21 -8
- package/dungeons/technical/retention-cadence.js +115 -112
- package/dungeons/technical/sanity.js +86 -80
- package/dungeons/technical/scale-test.js +34 -38
- package/dungeons/technical/scd.js +111 -128
- package/dungeons/technical/simple.js +134 -141
- package/dungeons/technical/simplest.js +54 -62
- package/dungeons/technical/text-generation.js +110 -146
- package/dungeons/vertical/ai-platform.js +296 -333
- package/dungeons/vertical/community.js +284 -255
- package/dungeons/vertical/crypto.js +395 -391
- package/dungeons/vertical/dating.js +411 -378
- package/dungeons/vertical/devtools.js +336 -298
- package/dungeons/vertical/ecommerce.js +316 -394
- package/dungeons/vertical/education.js +369 -325
- package/dungeons/vertical/fintech.js +358 -325
- package/dungeons/vertical/fitness.js +335 -291
- package/dungeons/vertical/food-delivery.js +343 -307
- package/dungeons/vertical/gaming.js +480 -444
- package/dungeons/vertical/healthcare.js +306 -262
- package/dungeons/vertical/insurance-application.js +427 -409
- package/dungeons/vertical/logistics.js +271 -252
- package/dungeons/vertical/marketplace.js +333 -323
- package/dungeons/vertical/media.js +382 -335
- package/dungeons/vertical/real-estate.js +395 -346
- package/dungeons/vertical/sass.js +319 -333
- package/dungeons/vertical/social.js +368 -316
- package/dungeons/vertical/travel.js +297 -295
- package/index.js +46 -4
- package/lib/core/config-validator.js +126 -28
- package/lib/generators/funnels.js +4 -1
- package/lib/orchestrators/mixpanel-sender.js +7 -0
- package/lib/orchestrators/user-loop.js +132 -31
- package/lib/templates/defaults.js +59 -59
- package/lib/templates/macro-presets.js +14 -2
- package/lib/utils/dataset-context.js +103 -0
- package/lib/utils/retention-curve.js +140 -0
- package/lib/utils/utils.js +149 -38
- package/lib/verify/counting.js +40 -0
- package/lib/verify/emulate-breakdown.js +20 -1
- package/lib/verify/index.js +1 -0
- package/lib/verify/schema-validator.js +3 -1
- package/package.json +11 -2
- package/scripts/run-dungeon.mjs +12 -1
- package/types.d.ts +117 -1
|
@@ -1,96 +1,57 @@
|
|
|
1
|
-
// ──
|
|
2
|
-
const SEED = "SUPER DUPER DANGEROUS BROOO";
|
|
3
|
-
const num_days = 92;
|
|
4
|
-
const num_users = 8_000;
|
|
5
|
-
const avg_events_per_user_per_day = 1.3;
|
|
6
|
-
let token = "";
|
|
7
|
-
|
|
8
|
-
// ── env overrides ──
|
|
9
|
-
if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
|
|
10
|
-
|
|
1
|
+
// ── IMPORTS ──
|
|
11
2
|
import dayjs from "dayjs";
|
|
12
3
|
import utc from "dayjs/plugin/utc.js";
|
|
13
4
|
import "dotenv/config";
|
|
14
|
-
import { weighNumRange,
|
|
5
|
+
import { weighNumRange, initChance, integer } from "../../lib/utils/utils.js";
|
|
15
6
|
import { createTextGenerator, generateBatch } from "../../lib/generators/text.js";
|
|
16
|
-
|
|
17
7
|
dayjs.extend(utc);
|
|
18
|
-
const chance = initChance(SEED);
|
|
19
|
-
|
|
20
8
|
/** @typedef {import("../../types").Dungeon} Dungeon */
|
|
21
9
|
|
|
10
|
+
// ── OVERVIEW ──
|
|
22
11
|
/*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* Scale: 8,000 users, ~960K events, 92 days
|
|
29
|
-
*
|
|
30
|
-
* A text-heavy SaaS analytics dungeon that exercises every text generation
|
|
31
|
-
* style available in dungeon-master. Users interact through support tickets, product
|
|
32
|
-
* reviews, forum posts, search queries, chat messages, social media posts
|
|
33
|
-
* (Twitter, LinkedIn, Reddit), bug reports, feature requests, onboarding
|
|
34
|
-
* feedback, charity/wedding comments, and webinar chat.
|
|
35
|
-
*
|
|
36
|
-
* Each event type uses a dedicated createTextGenerator() configured with its
|
|
37
|
-
* own style, tone, formality, keyword banks, typo rates, and authenticity
|
|
38
|
-
* levels. Text generator styles used: support, review, forum, search,
|
|
39
|
-
* feedback, chat, email, tweet, comments (9 distinct styles).
|
|
40
|
-
*
|
|
41
|
-
* Events:
|
|
42
|
-
* social_media_tweet (15) > chat_message (10) > charity_comment_posted (8)
|
|
43
|
-
* > wedding_comment_posted (6) > company_announcement_tweet (3)
|
|
44
|
-
* > all others (1 each): support ticket, review, forum post, search,
|
|
45
|
-
* feedback, email, twitter, linkedin, reddit, bug report, feature
|
|
46
|
-
* request, onboarding, tutorial comment, webinar chat, api thread
|
|
47
|
-
* ============================================================================
|
|
12
|
+
* NAME: text-generation
|
|
13
|
+
* PURPOSE: text-heavy fixture exercising every createTextGenerator style — support, review, forum, search, feedback, chat, email, tweet, comments
|
|
14
|
+
* SCALE: 8,000 users, ~960K events, 92 days
|
|
15
|
+
* EVENTS (20): enterprise_support_ticket, casual_product_review, technical_forum_post, search_query, business_feedback, chat_message (10), email_correspondence, twitter_post, linkedin_post, reddit_comment, bug_report_submitted, feature_request_submitted, onboarding_feedback, tutorial_comment_posted, webinar_chat_message, charity_comment_posted (8), wedding_comment_posted (6), social_media_tweet (15), company_announcement_tweet (3), api_discussion_thread, satisfaction_survey_triggered (hook-injected)
|
|
16
|
+
* FUNNELS (0): none (alsoInferFunnels: true)
|
|
48
17
|
*/
|
|
49
18
|
|
|
19
|
+
// ── HOOK STORIES ──
|
|
50
20
|
/*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
21
|
+
* Hook 1: Power User + Churn Risk Classification (user)
|
|
22
|
+
* Users with engagement_score > 70 get is_power_user = true.
|
|
23
|
+
* Users with last_active_days_ago > 20 get risk_level = "high_churn", else "healthy".
|
|
24
|
+
* Mixpanel: Insights — user profile breakdown by "is_power_user" / "risk_level"
|
|
25
|
+
* cross-referenced with "user_tier".
|
|
54
26
|
*
|
|
55
|
-
* Hook
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* Mixpanel report:
|
|
60
|
-
* - Insights > user profile breakdown by "is_power_user"
|
|
61
|
-
* - Insights > user profile breakdown by "risk_level", cross-reference
|
|
62
|
-
* with "user_tier" to see churn risk by plan
|
|
27
|
+
* Hook 2: Critical Ticket Auto-Escalation (event)
|
|
28
|
+
* enterprise_support_ticket events with priority = "critical" get
|
|
29
|
+
* escalation_level bumped by 1 (max 3) and auto_escalated = true.
|
|
30
|
+
* Mixpanel: Insights — "enterprise_support_ticket" total, breakdown by auto_escalated.
|
|
63
31
|
*
|
|
64
|
-
* Hook
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* Mixpanel report:
|
|
69
|
-
* - Insights > "enterprise_support_ticket" total events, breakdown by
|
|
70
|
-
* auto_escalated
|
|
71
|
-
* - Expect: critical tickets show auto_escalated = true
|
|
32
|
+
* Hook 3: Critical Bug Flagging (event)
|
|
33
|
+
* bug_report_submitted with severity = "critical" AND is_reproducible = true
|
|
34
|
+
* get requires_immediate_review = true and random estimated_fix_hours (1-8).
|
|
35
|
+
* Mixpanel: Insights — "bug_report_submitted" total, breakdown by requires_immediate_review.
|
|
72
36
|
*
|
|
73
|
-
* Hook
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* random estimated_fix_hours (1-8).
|
|
78
|
-
* Mixpanel report:
|
|
79
|
-
* - Insights > "bug_report_submitted" total events, breakdown by
|
|
80
|
-
* requires_immediate_review
|
|
81
|
-
* - Expect: only critical + reproducible bugs are flagged
|
|
82
|
-
*
|
|
83
|
-
* Hook 4: Enterprise Satisfaction Survey Injection
|
|
84
|
-
* Type: everything
|
|
85
|
-
* What: Enterprise-tier users with > 5 events get a
|
|
86
|
-
* "satisfaction_survey_triggered" event appended with a 1-10 NPS score.
|
|
87
|
-
* Mixpanel report:
|
|
88
|
-
* - Insights > "satisfaction_survey_triggered" AVG(score), breakdown by
|
|
89
|
-
* product_tier
|
|
90
|
-
* - Expect: only enterprise users have survey events
|
|
91
|
-
* ============================================================================
|
|
37
|
+
* Hook 4: Enterprise Satisfaction Survey Injection (everything)
|
|
38
|
+
* Enterprise-tier users with > 5 events get a "satisfaction_survey_triggered"
|
|
39
|
+
* event appended with a 1-10 NPS score.
|
|
40
|
+
* Mixpanel: Insights — "satisfaction_survey_triggered" AVG(score), breakdown by product_tier.
|
|
92
41
|
*/
|
|
93
42
|
|
|
43
|
+
// ── SCALE ──
|
|
44
|
+
const SEED = "SUPER DUPER DANGEROUS BROOO";
|
|
45
|
+
const NUM_DAYS = 92;
|
|
46
|
+
const NUM_USERS = 8_000;
|
|
47
|
+
const EVENTS_PER_DAY = 1.3;
|
|
48
|
+
const token = process.env.MP_TOKEN || "";
|
|
49
|
+
|
|
50
|
+
const chance = initChance(SEED);
|
|
51
|
+
|
|
52
|
+
// ── DATA ARRAYS ──
|
|
53
|
+
// (text generator instances — one per event style)
|
|
54
|
+
|
|
94
55
|
// Enterprise support ticket generator with keywords and high authenticity
|
|
95
56
|
const enterpriseSupportGen = createTextGenerator({
|
|
96
57
|
style: "support",
|
|
@@ -402,32 +363,81 @@ const webinarChatGen = createTextGenerator({
|
|
|
402
363
|
includeMetadata: false
|
|
403
364
|
});
|
|
404
365
|
|
|
366
|
+
// ── HELPER FUNCTIONS ──
|
|
367
|
+
function handleUserHook(record) {
|
|
368
|
+
// H1: classify users by engagement and tier
|
|
369
|
+
record.is_power_user = record.engagement_score > 70;
|
|
370
|
+
record.risk_level = record.last_active_days_ago > 20 ? "high_churn" : "healthy";
|
|
371
|
+
return record;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function handleEventHook(record) {
|
|
375
|
+
// H2: enterprise support tickets get auto-escalated if critical
|
|
376
|
+
if (record.event === "enterprise_support_ticket" && record.priority === "critical") {
|
|
377
|
+
record.escalation_level = Math.min((record.escalation_level || 1) + 1, 3);
|
|
378
|
+
record.auto_escalated = true;
|
|
379
|
+
}
|
|
380
|
+
// H3: bug reports with critical severity get flagged for immediate review
|
|
381
|
+
if (record.event === "bug_report_submitted" && record.severity === "critical" && record.is_reproducible === true) {
|
|
382
|
+
record.requires_immediate_review = true;
|
|
383
|
+
record.estimated_fix_hours = chance.integer({ min: 1, max: 8 });
|
|
384
|
+
}
|
|
385
|
+
return record;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function handleEverythingHook(record, meta) {
|
|
389
|
+
if (!meta || !meta.profile) return record;
|
|
390
|
+
// stamp superProps from profile for consistency (skip feature_flags — intentionally per-event)
|
|
391
|
+
const profile = meta.profile;
|
|
392
|
+
record.forEach(e => {
|
|
393
|
+
e.product_tier = profile.product_tier;
|
|
394
|
+
e.data_center = profile.data_center;
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// H4: enterprise users with > 5 events get a satisfaction_survey_triggered event appended
|
|
398
|
+
if (meta.profile.user_tier === "enterprise" && record.length > 5) {
|
|
399
|
+
const lastEvent = record[record.length - 1];
|
|
400
|
+
record.push({
|
|
401
|
+
...lastEvent,
|
|
402
|
+
event: "satisfaction_survey_triggered",
|
|
403
|
+
time: lastEvent.time,
|
|
404
|
+
user_id: lastEvent.user_id,
|
|
405
|
+
survey_type: "quarterly_nps",
|
|
406
|
+
score: chance.integer({ min: 1, max: 10 }),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return record;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── CONFIG ──
|
|
405
413
|
/** @type {Dungeon} */
|
|
406
414
|
const dungeon = {
|
|
407
415
|
seed: SEED,
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
numUsers: num_users,
|
|
412
|
-
hasAnonIds: false,
|
|
413
|
-
hasSessionIds: true, // Enable for chat flow tracking
|
|
416
|
+
numDays: NUM_DAYS,
|
|
417
|
+
avgEventsPerUserPerDay: EVENTS_PER_DAY,
|
|
418
|
+
numUsers: NUM_USERS,
|
|
414
419
|
format: "json",
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
420
|
+
credentials: {
|
|
421
|
+
token,
|
|
422
|
+
},
|
|
423
|
+
switches: {
|
|
424
|
+
hasSessionIds: true, // Enable for chat flow tracking
|
|
425
|
+
alsoInferFunnels: true,
|
|
426
|
+
hasLocation: true,
|
|
427
|
+
hasAndroidDevices: false,
|
|
428
|
+
hasIOSDevices: false,
|
|
429
|
+
hasDesktopDevices: false,
|
|
430
|
+
hasBrowser: false,
|
|
431
|
+
hasCampaigns: false,
|
|
432
|
+
isAnonymous: false,
|
|
433
|
+
hasAdSpend: false,
|
|
434
|
+
hasAvatar: false,
|
|
435
|
+
},
|
|
425
436
|
|
|
426
437
|
concurrency: 1,
|
|
427
438
|
writeToDisk: false,
|
|
428
439
|
|
|
429
|
-
|
|
430
|
-
events: [
|
|
440
|
+
events: [
|
|
431
441
|
{
|
|
432
442
|
event: "enterprise_support_ticket",
|
|
433
443
|
weight: 1,
|
|
@@ -780,60 +790,14 @@ const dungeon = {
|
|
|
780
790
|
product_tier: ["free", "basic", "pro", "enterprise"],
|
|
781
791
|
data_center: ["us-east", "us-west", "eu-central", "asia-pacific"],
|
|
782
792
|
},
|
|
783
|
-
|
|
784
|
-
// ============= Slowly Changing Dimensions =============
|
|
793
|
+
|
|
785
794
|
scdProps: {},
|
|
786
|
-
|
|
787
|
-
// ============= Lookup Tables =============
|
|
788
|
-
lookupTables: [
|
|
789
|
-
|
|
790
|
-
],
|
|
795
|
+
lookupTables: [],
|
|
791
796
|
|
|
792
797
|
hook: function (record, type, meta) {
|
|
793
|
-
|
|
794
|
-
if (type === "
|
|
795
|
-
|
|
796
|
-
record.risk_level = record.last_active_days_ago > 20 ? "high_churn" : "healthy";
|
|
797
|
-
return record;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
// --- event hook: enterprise support tickets get auto-escalated if critical ---
|
|
801
|
-
if (type === "event") {
|
|
802
|
-
if (record.event === "enterprise_support_ticket" && record.priority === "critical") {
|
|
803
|
-
record.escalation_level = Math.min((record.escalation_level || 1) + 1, 3);
|
|
804
|
-
record.auto_escalated = true;
|
|
805
|
-
}
|
|
806
|
-
// bug reports with critical severity get flagged for immediate review
|
|
807
|
-
if (record.event === "bug_report_submitted" && record.severity === "critical" && record.is_reproducible === true) {
|
|
808
|
-
record.requires_immediate_review = true;
|
|
809
|
-
record.estimated_fix_hours = chance.integer({ min: 1, max: 8 });
|
|
810
|
-
}
|
|
811
|
-
return record;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// --- everything hook: enterprise users get a satisfaction survey event ---
|
|
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
|
-
|
|
823
|
-
if (meta.profile.user_tier === "enterprise" && record.length > 5) {
|
|
824
|
-
const lastEvent = record[record.length - 1];
|
|
825
|
-
record.push({
|
|
826
|
-
...lastEvent,
|
|
827
|
-
event: "satisfaction_survey_triggered",
|
|
828
|
-
time: lastEvent.time,
|
|
829
|
-
user_id: lastEvent.user_id,
|
|
830
|
-
survey_type: "quarterly_nps",
|
|
831
|
-
score: chance.integer({ min: 1, max: 10 }),
|
|
832
|
-
});
|
|
833
|
-
}
|
|
834
|
-
return record;
|
|
835
|
-
}
|
|
836
|
-
|
|
798
|
+
if (type === "user") return handleUserHook(record);
|
|
799
|
+
if (type === "event") return handleEventHook(record);
|
|
800
|
+
if (type === "everything") return handleEverythingHook(record, meta);
|
|
837
801
|
return record;
|
|
838
802
|
}
|
|
839
803
|
};
|