@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.
Files changed (66) hide show
  1. package/.claude/skills/create-dungeon/SKILL.md +139 -46
  2. package/.claude/skills/verify-dungeon/references/counting-semantics.md +31 -6
  3. package/.claude/skills/verify-dungeon/references/sql-recipes.md +44 -25
  4. package/.claude/skills/write-hooks/SKILL.md +31 -3
  5. package/CHANGELOG.md +85 -0
  6. package/HOOKS.md +13 -0
  7. package/dungeons/technical/ad-spend.js +41 -49
  8. package/dungeons/technical/anonymous-users.js +38 -36
  9. package/dungeons/technical/array-of-object-lookup.js +136 -153
  10. package/dungeons/technical/datagen-v15-verify.js +24 -11
  11. package/dungeons/technical/experiments.js +42 -40
  12. package/dungeons/technical/foobar.js +114 -118
  13. package/dungeons/technical/group-analytics.js +42 -40
  14. package/dungeons/technical/hook-helpers-verify.js +69 -50
  15. package/dungeons/technical/identity-model-verify.js +22 -12
  16. package/dungeons/technical/mirror-strategies.js +37 -39
  17. package/dungeons/technical/nested-objects.js +119 -118
  18. package/dungeons/technical/pattern-aggregate-by-bin.js +21 -8
  19. package/dungeons/technical/pattern-attributed-by-source.js +23 -9
  20. package/dungeons/technical/pattern-frequency-by-frequency.js +21 -8
  21. package/dungeons/technical/pattern-funnel-frequency.js +30 -15
  22. package/dungeons/technical/pattern-ttc-by-segment.js +21 -8
  23. package/dungeons/technical/retention-cadence.js +115 -112
  24. package/dungeons/technical/sanity.js +86 -80
  25. package/dungeons/technical/scale-test.js +34 -38
  26. package/dungeons/technical/scd.js +111 -128
  27. package/dungeons/technical/simple.js +134 -141
  28. package/dungeons/technical/simplest.js +54 -62
  29. package/dungeons/technical/text-generation.js +110 -146
  30. package/dungeons/vertical/ai-platform.js +296 -333
  31. package/dungeons/vertical/community.js +284 -255
  32. package/dungeons/vertical/crypto.js +395 -391
  33. package/dungeons/vertical/dating.js +411 -378
  34. package/dungeons/vertical/devtools.js +336 -298
  35. package/dungeons/vertical/ecommerce.js +316 -394
  36. package/dungeons/vertical/education.js +369 -325
  37. package/dungeons/vertical/fintech.js +358 -325
  38. package/dungeons/vertical/fitness.js +335 -291
  39. package/dungeons/vertical/food-delivery.js +343 -307
  40. package/dungeons/vertical/gaming.js +480 -444
  41. package/dungeons/vertical/healthcare.js +306 -262
  42. package/dungeons/vertical/insurance-application.js +427 -409
  43. package/dungeons/vertical/logistics.js +271 -252
  44. package/dungeons/vertical/marketplace.js +333 -323
  45. package/dungeons/vertical/media.js +382 -335
  46. package/dungeons/vertical/real-estate.js +395 -346
  47. package/dungeons/vertical/sass.js +319 -333
  48. package/dungeons/vertical/social.js +368 -316
  49. package/dungeons/vertical/travel.js +297 -295
  50. package/index.js +46 -4
  51. package/lib/core/config-validator.js +126 -28
  52. package/lib/generators/funnels.js +4 -1
  53. package/lib/orchestrators/mixpanel-sender.js +7 -0
  54. package/lib/orchestrators/user-loop.js +132 -31
  55. package/lib/templates/defaults.js +59 -59
  56. package/lib/templates/macro-presets.js +14 -2
  57. package/lib/utils/dataset-context.js +103 -0
  58. package/lib/utils/retention-curve.js +140 -0
  59. package/lib/utils/utils.js +149 -38
  60. package/lib/verify/counting.js +40 -0
  61. package/lib/verify/emulate-breakdown.js +20 -1
  62. package/lib/verify/index.js +1 -0
  63. package/lib/verify/schema-validator.js +3 -1
  64. package/package.json +11 -2
  65. package/scripts/run-dungeon.mjs +12 -1
  66. package/types.d.ts +117 -1
@@ -1,76 +1,71 @@
1
- // ── TWEAK THESE ──
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
- let token = "";
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
- /** @typedef {import("../../types").Dungeon} Config */
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
- region: "US",
63
- hasAnonIds: false, //if true, anonymousIds are created for each user
64
- hasSessionIds: false, //if true, hasSessionIds are created for each user
65
- hasAdSpend: false,
66
- hasLocation: false,
67
- hasAndroidDevices: false,
68
- hasIOSDevices: false,
69
- hasDesktopDevices: false,
70
- hasBrowser: false,
71
- hasCampaigns: false,
72
- isAnonymous: false,
73
- alsoInferFunnels: false,
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
- // ── 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_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, range, date, initChance, exhaust, choose, integer } from "../../lib/utils/utils.js";
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
- * DATASET OVERVIEW
25
- * ============================================================================
26
- *
27
- * App: Text Generation Demo — showcases dungeon-master's organic text generation
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
- * ANALYTICS HOOKS
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 1: Power User + Churn Risk Classification
56
- * Type: user
57
- * What: Users with engagement_score > 70 get is_power_user = true.
58
- * Users inactive > 20 days get risk_level = "high_churn", else "healthy".
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 2: Critical Ticket Auto-Escalation
65
- * Type: event
66
- * What: enterprise_support_ticket events with priority = "critical" get
67
- * escalation_level bumped by 1 (max 3) and auto_escalated = true.
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 3: Critical Bug Flagging
74
- * Type: event
75
- * What: bug_report_submitted events with severity = "critical" AND
76
- * is_reproducible = true get requires_immediate_review = true and a
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
- token,
409
- numDays: num_days,
410
- avgEventsPerUserPerDay: avg_events_per_user_per_day,
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
- alsoInferFunnels: true,
416
- hasLocation: true,
417
- hasAndroidDevices: false,
418
- hasIOSDevices: false,
419
- hasDesktopDevices: false,
420
- hasBrowser: false,
421
- hasCampaigns: false,
422
- isAnonymous: false,
423
- hasAdSpend: false,
424
- hasAvatar: false,
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
- // ============= Enhanced Events with New API =============
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
- // --- user hook: classify users by engagement and tier ---
794
- if (type === "user") {
795
- record.is_power_user = record.engagement_score > 70;
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
  };