@ak--47/dungeon-master 1.5.0 → 1.5.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/.claude/skills/create-dungeon/SKILL.md +139 -46
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +31 -6
- package/.claude/skills/verify-dungeon/references/sql-recipes.md +44 -25
- package/.claude/skills/write-hooks/SKILL.md +31 -3
- package/CHANGELOG.md +85 -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,76 +1,71 @@
|
|
|
1
|
-
// ──
|
|
1
|
+
// ── IMPORTS ──
|
|
2
|
+
import Chance from 'chance';
|
|
3
|
+
let chance = new Chance();
|
|
4
|
+
import { pickAWinner, weighNumRange, integer, weighChoices, dateRange, listOf, objectList } from "../../lib/utils/utils.js";
|
|
5
|
+
/** @typedef {import("../../types").Dungeon} Config */
|
|
6
|
+
|
|
7
|
+
// ── OVERVIEW ──
|
|
8
|
+
/*
|
|
9
|
+
* NAME: simplest
|
|
10
|
+
* PURPOSE: Engine-validation baseline — no hooks. Engine + TimeSoup alone produce the trend shape. Used by engine-shape canary + full sweep.
|
|
11
|
+
* SCALE: 2,500 users, ~250K events, 100 days
|
|
12
|
+
* EVENTS (25): page view (10) > view item (9) > view category (8) > login (8) > search (7) > notification received (7) > watch video (6) > add to cart (5) > notification clicked (5) > rate item (4) > add to wishlist (4) > checkout (3) > remove from cart (3) > update profile (3) > compare items (3) > share content (2) > support ticket (2) > apply coupon (2) > leave review (2) > sign up (1) > add payment method (1) > invite friend (1) > save address (1) > subscribe newsletter (1) > redeem reward (1)
|
|
13
|
+
* FUNNELS (11): Signup Funnel, Purchase Funnel, Content Engagement, Review Funnel, Browse to Cart, Notification Engagement, Wishlist to Purchase, Post-Purchase Advocacy, Coupon Purchase Flow, Newsletter to Purchase, Support Recovery
|
|
14
|
+
* USER PROPS: theme, title, spiritAnimal, luckyNumber, emailOptIn, signupDate, favoriteCategories, preferences, recentOrders
|
|
15
|
+
* SUPER PROPS: theme
|
|
16
|
+
* GROUPS: none
|
|
17
|
+
*
|
|
18
|
+
* USE CASES:
|
|
19
|
+
* - Engine sweep harness baseline (`scripts/sweep-engine.mjs`). Strict-bar regression
|
|
20
|
+
* testing across the 184-combo macro/born/rate/active-day matrix.
|
|
21
|
+
* - Reference for "what an average dungeon looks like" — diverse events + funnels +
|
|
22
|
+
* user props with no hook architecting on top.
|
|
23
|
+
* - Starting template for new dungeons before adding bespoke hooks.
|
|
24
|
+
* - At least one property of every Mixpanel data type:
|
|
25
|
+
* string, numeric, boolean, date, list, object, list-of-objects
|
|
26
|
+
* on both event properties and user properties.
|
|
27
|
+
* - No device/location/campaign data — pure event stream.
|
|
28
|
+
* - **No hooks.** This is the engine-validation baseline; engine + TimeSoup alone
|
|
29
|
+
* produce the trend shape. Hook authors should reach for vertical dungeons or
|
|
30
|
+
* write hooks against this template; do NOT add hooks here.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// ── SCALE ──
|
|
2
34
|
const SEED = "simple is best";
|
|
3
35
|
const num_days = 100;
|
|
4
36
|
const num_users = 2_500;
|
|
5
37
|
const avg_events_per_user_per_day = 1;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// ── env overrides ──
|
|
9
|
-
if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
|
|
10
|
-
|
|
11
|
-
import Chance from 'chance';
|
|
12
|
-
let chance = new Chance();
|
|
13
|
-
import dayjs from "dayjs";
|
|
14
|
-
import utc from "dayjs/plugin/utc.js";
|
|
15
|
-
dayjs.extend(utc);
|
|
16
|
-
import { uid, comma } from 'ak-tools';
|
|
17
|
-
import { pickAWinner, weighNumRange, date, integer, weighChoices, dateRange, listOf, objectList } from "../../lib/utils/utils.js";
|
|
38
|
+
const token = process.env.MP_TOKEN || "";
|
|
18
39
|
|
|
19
|
-
|
|
40
|
+
// ── DATA ARRAYS ──
|
|
20
41
|
const itemCategories = ["Books", "Movies", "Music", "Games", "Electronics", "Computers", "Smart Home", "Home", "Garden", "Pet", "Beauty", "Health", "Toys", "Kids", "Baby", "Handmade", "Sports", "Outdoors", "Automotive", "Industrial", "Entertainment", "Art", "Food", "Appliances", "Office", "Wedding", "Software"];
|
|
21
42
|
|
|
22
43
|
const videoCategories = ["funny", "educational", "inspirational", "music", "news", "sports", "cooking", "DIY", "travel", "gaming"];
|
|
23
44
|
|
|
24
|
-
|
|
25
|
-
* ═══════════════════════════════════════════════════════════════
|
|
26
|
-
* DATASET OVERVIEW
|
|
27
|
-
* ═══════════════════════════════════════════════════════════════
|
|
28
|
-
*
|
|
29
|
-
* Simplest E-Commerce App — the canonical no-hook baseline dungeon.
|
|
30
|
-
* - 2,500 users over 100 days, ~250K events
|
|
31
|
-
* - 25 event types covering browse, search, cart, checkout,
|
|
32
|
-
* notifications, reviews, wishlists, rewards
|
|
33
|
-
* - 11 funnels with diverse weights (1-10) covering signup, purchase,
|
|
34
|
-
* content engagement, browse-to-cart, post-purchase advocacy, etc.
|
|
35
|
-
* - At least one property of every Mixpanel data type:
|
|
36
|
-
* string, numeric, boolean, date, list, object, list-of-objects
|
|
37
|
-
* on both event properties and user properties.
|
|
38
|
-
* - No device/location/campaign data — pure event stream
|
|
39
|
-
* - **No hooks.** This is the engine-validation baseline; engine + TimeSoup
|
|
40
|
-
* alone produce the trend shape. Hook authors should reach for vertical
|
|
41
|
-
* dungeons or write hooks against this template; do NOT add hooks here.
|
|
42
|
-
*
|
|
43
|
-
* ═══════════════════════════════════════════════════════════════
|
|
44
|
-
* USE CASES
|
|
45
|
-
* ═══════════════════════════════════════════════════════════════
|
|
46
|
-
*
|
|
47
|
-
* - Engine sweep harness baseline (`scripts/sweep-engine.mjs`). Strict-bar
|
|
48
|
-
* regression testing across the 184-combo macro/born/rate/active-day matrix.
|
|
49
|
-
* - Reference for "what an average dungeon looks like" — diverse events
|
|
50
|
-
* + funnels + user props with no hook architecting on top.
|
|
51
|
-
* - Starting template for new dungeons before adding bespoke hooks.
|
|
52
|
-
*/
|
|
53
|
-
|
|
45
|
+
// ── CONFIG ──
|
|
54
46
|
/** @type {import('../types.js').Dungeon} */
|
|
55
47
|
const config = {
|
|
56
|
-
token,
|
|
57
48
|
seed: SEED,
|
|
58
49
|
numDays: num_days,
|
|
59
50
|
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
60
51
|
numUsers: num_users,
|
|
61
52
|
format: 'json', //csv or json
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
53
|
+
credentials: {
|
|
54
|
+
token,
|
|
55
|
+
region: "US",
|
|
56
|
+
},
|
|
57
|
+
switches: {
|
|
58
|
+
hasSessionIds: false, //if true, hasSessionIds are created for each user
|
|
59
|
+
hasAdSpend: false,
|
|
60
|
+
hasLocation: false,
|
|
61
|
+
hasAndroidDevices: false,
|
|
62
|
+
hasIOSDevices: false,
|
|
63
|
+
hasDesktopDevices: false,
|
|
64
|
+
hasBrowser: false,
|
|
65
|
+
hasCampaigns: false,
|
|
66
|
+
isAnonymous: false,
|
|
67
|
+
alsoInferFunnels: false,
|
|
68
|
+
},
|
|
74
69
|
concurrency: 1,
|
|
75
70
|
writeToDisk: false,
|
|
76
71
|
|
|
@@ -447,7 +442,7 @@ const config = {
|
|
|
447
442
|
mirrorProps: {},
|
|
448
443
|
|
|
449
444
|
/*
|
|
450
|
-
for group analytics keys, we need an array of arrays [[],[],[]]
|
|
445
|
+
for group analytics keys, we need an array of arrays [[],[],[]]
|
|
451
446
|
each pair represents a group_key and the number of profiles for that key
|
|
452
447
|
*/
|
|
453
448
|
groupKeys: [],
|
|
@@ -457,7 +452,4 @@ const config = {
|
|
|
457
452
|
// alone produce the trend shape. To experiment with hooks, fork this file.
|
|
458
453
|
};
|
|
459
454
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
export default config;
|
|
455
|
+
export default config;
|
|
@@ -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
|
};
|