@ak--47/dungeon-master 1.3.0 → 1.3.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.
Files changed (74) hide show
  1. package/dungeons/technical/array-of-object-lookup.js +0 -2
  2. package/dungeons/technical/simple.js +3 -4
  3. package/dungeons/technical/simplest-schema.json +5 -0
  4. package/dungeons/technical/text-generation.js +1 -1
  5. package/dungeons/vertical/ai-platform.js +192 -133
  6. package/dungeons/vertical/community.js +64 -34
  7. package/dungeons/vertical/crypto.js +333 -224
  8. package/dungeons/vertical/dating.js +228 -282
  9. package/dungeons/vertical/devtools.js +164 -62
  10. package/dungeons/vertical/ecommerce.js +240 -91
  11. package/dungeons/vertical/education.js +328 -310
  12. package/dungeons/vertical/fintech.js +437 -311
  13. package/dungeons/vertical/fitness.js +141 -58
  14. package/dungeons/vertical/food-delivery.js +298 -318
  15. package/dungeons/vertical/gaming.js +378 -231
  16. package/dungeons/vertical/healthcare.js +140 -60
  17. package/dungeons/vertical/insurance-application.js +168 -73
  18. package/dungeons/vertical/logistics.js +95 -6
  19. package/dungeons/vertical/marketplace.js +137 -51
  20. package/dungeons/vertical/media.js +241 -386
  21. package/dungeons/vertical/real-estate.js +322 -315
  22. package/dungeons/vertical/sass.js +253 -263
  23. package/dungeons/vertical/social.js +262 -206
  24. package/dungeons/vertical/travel.js +59 -16
  25. package/index.js +17 -17
  26. package/lib/core/config-validator.js +93 -21
  27. package/lib/core/context.js +10 -24
  28. package/lib/core/storage.js +6 -1
  29. package/lib/generators/events.js +11 -14
  30. package/lib/generators/funnels.js +12 -4
  31. package/lib/generators/mirror.js +3 -2
  32. package/lib/generators/product-names.js +1 -1
  33. package/lib/generators/scd.js +2 -1
  34. package/lib/generators/text.js +1 -1
  35. package/lib/orchestrators/user-loop.js +47 -47
  36. package/lib/utils/utils.js +71 -39
  37. package/package.json +1 -1
  38. package/scripts/smoke-test-all.mjs +162 -0
  39. package/scripts/verify-runner.mjs +72 -24
  40. package/types.d.ts +38 -15
  41. package/dungeons/technical/ad-spend-schema.json +0 -128
  42. package/dungeons/technical/anonymous-users-schema.json +0 -92
  43. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  44. package/dungeons/technical/experiments-schema.json +0 -203
  45. package/dungeons/technical/foobar-schema.json +0 -362
  46. package/dungeons/technical/group-analytics-schema.json +0 -241
  47. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  48. package/dungeons/technical/nested-objects-schema.json +0 -145
  49. package/dungeons/technical/retention-cadence-schema.json +0 -37
  50. package/dungeons/technical/sanity-schema.json +0 -185
  51. package/dungeons/technical/scale-test-schema.json +0 -70
  52. package/dungeons/technical/scd-schema.json +0 -467
  53. package/dungeons/technical/simple-schema.json +0 -362
  54. package/dungeons/technical/text-generation-schema.json +0 -1062
  55. package/dungeons/vertical/ai-platform-schema.json +0 -617
  56. package/dungeons/vertical/community-schema.json +0 -579
  57. package/dungeons/vertical/crypto-schema.json +0 -546
  58. package/dungeons/vertical/dating-schema.json +0 -401
  59. package/dungeons/vertical/devtools-schema.json +0 -601
  60. package/dungeons/vertical/ecommerce-schema.json +0 -604
  61. package/dungeons/vertical/education-schema.json +0 -5686
  62. package/dungeons/vertical/fintech-schema.json +0 -630
  63. package/dungeons/vertical/fitness-schema.json +0 -530
  64. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  65. package/dungeons/vertical/gaming-schema.json +0 -2703
  66. package/dungeons/vertical/healthcare-schema.json +0 -549
  67. package/dungeons/vertical/insurance-application-schema.json +0 -485
  68. package/dungeons/vertical/logistics-schema.json +0 -574
  69. package/dungeons/vertical/marketplace-schema.json +0 -533
  70. package/dungeons/vertical/media-schema.json +0 -4749
  71. package/dungeons/vertical/real-estate-schema.json +0 -527
  72. package/dungeons/vertical/sass-schema.json +0 -3128
  73. package/dungeons/vertical/social-schema.json +0 -620
  74. package/dungeons/vertical/travel-schema.json +0 -580
@@ -52,153 +52,217 @@ const chance = u.initChance(SEED);
52
52
 
53
53
  /*
54
54
  * ═══════════════════════════════════════════════════════════════════════════════
55
- * ANALYTICS HOOKS
55
+ * ANALYTICS HOOKS (10 hooks)
56
56
  * ═══════════════════════════════════════════════════════════════════════════════
57
57
  *
58
- * 8 deliberately architected patterns hidden in the data, simulating real-world
59
- * B2B SaaS behavior. Several hooks use event removal (splice), event replacement,
60
- * and module-level closure state tracking via Map objects.
58
+ * 10 deliberately architected patterns hidden in the data. NOTE: All cohort
59
+ * effects are HIDDEN no flag stamping. Discoverable via behavioral cohorts
60
+ * or raw-prop breakdowns (company_size, day, doc_section). Adds:
61
+ * 9. INCIDENT RESPONSE TIME-TO-CONVERT (Enterprise 1.4x faster vs Startup)
62
+ * [funnel-post: visible only in Mixpanel funnel median TTC; cross-event
63
+ * MIN→MIN SQL queries do NOT show this]
64
+ * 10. DOCS MAGIC NUMBER (sweet 4-7 docs → +40% deploys; over 8+ → drop 25%)
61
65
  *
62
66
  * ─────────────────────────────────────────────────────────────────────────────
63
- * 1. END-OF-QUARTER SPIKE (event hook)
67
+ * 1. END-OF-QUARTER SPIKE (event)
64
68
  * ─────────────────────────────────────────────────────────────────────────────
65
69
  *
66
- * Days 80-90: billing events shift toward plan upgrades 40% of the time, and team
67
- * member invitations are duplicated 50% of the time. Tagged: quarter_end_push: true.
70
+ * PATTERN: Days 80-90 billing events shift event_type toward "plan_upgraded"
71
+ * 40% of the time and team member invitations are duplicated 50% of the time.
72
+ * No flag — discover via line chart by day.
68
73
  *
69
- * Mixpanel Report Plan Upgrades Over Time:
70
- * • Insights line chart
71
- * • Event: "billing event", filter "event_type" = "plan_upgraded"
72
- * • Daily trend
73
- * • Expected: Spike in plan upgrades during days 80-90 (4x normal volume)
74
+ * HOW TO FIND IT IN MIXPANEL:
74
75
  *
75
- * Mixpanel Report Team Expansion Surge:
76
- * Insights line chart
77
- * Event: "team member invited", filter "quarter_end_push" = true
78
- * Daily trend
79
- * Expected: Clear volume spike in last 10 days with duplicate invites
76
+ * Report 1: Plan Upgrades Over Time
77
+ * - Report type: Insights
78
+ * - Event: "billing event"
79
+ * - Measure: Total
80
+ * - Filter: event_type = "plan_upgraded"
81
+ * - Line chart by day
82
+ * - Expected: ~4x normal upgrade volume during days 80-90
83
+ *
84
+ * Report 2: Team Expansion Surge
85
+ * - Report type: Insights
86
+ * - Event: "team member invited"
87
+ * - Measure: Total
88
+ * - Line chart by day
89
+ * - Expected: clear volume spike in the final 10 days from duplicated invites
90
+ *
91
+ * REAL-WORLD ANALOGUE: B2B SaaS revenue clusters at quarter-close as sales
92
+ * teams pull deals forward and customers expand seats to lock in pricing.
80
93
  *
81
94
  * ─────────────────────────────────────────────────────────────────────────────
82
- * 2. CHURNED ACCOUNT SILENCING (everything hook)
95
+ * 2. CHURNED ACCOUNT SILENCING (everything)
83
96
  * ─────────────────────────────────────────────────────────────────────────────
84
97
  *
85
- * ~10% of users (hash of distinct_id, idHash % 5 === 0) go completely silent
86
- * after day 30. ALL events after month 1 are removed via splice(). User profiles
87
- * are tagged churned_account: true for discoverability.
98
+ * PATTERN: ~10-20% of users (deterministic via distinct_id char hash) go
99
+ * completely silent after day 30. All post-d30 events are removed via splice().
100
+ * No flag derive cohort via behavioral retention bucket (users with zero
101
+ * activity past d30 vs the rest).
102
+ *
103
+ * HOW TO FIND IT IN MIXPANEL:
104
+ *
105
+ * Report 1: Retention Cliff
106
+ * - Cohort A: users with at least 1 event AFTER day 30
107
+ * - Cohort B: users with events ONLY in days 1-30
108
+ * - Compare cohort sizes — B should be ~10-20% of total
88
109
  *
89
- * Mixpanel Report Churned Account Retention:
90
- * Retention report
91
- * Event A/B: Any event
92
- * Breakdown: User profile "churned_account"
93
- * Expected: churned_account=true shows 0% retention after day 30
110
+ * Report 2: Activity Volume Pre/Post Day 30
111
+ * - Report type: Insights
112
+ * - Event: any event
113
+ * - Measure: Total per user
114
+ * - Line chart by day
115
+ * - Expected: a visible drop after d30 driven by the silent cohort
94
116
  *
95
- * Mixpanel ReportChurned Account Activity:
96
- * Insights line chart
97
- * • Event: Any event, measure total per user
98
- * • Breakdown: User profile "churned_account"
99
- * • Weekly trend
100
- * • Expected: churned_account=true flatlines after week 4
117
+ * REAL-WORLD ANALOGUE: Most SaaS churn happens silently accounts simply
118
+ * stop logging in long before the formal cancellation lands.
101
119
  *
102
120
  * ─────────────────────────────────────────────────────────────────────────────
103
- * 3. ALERT ESCALATION REPLACEMENT (event hook)
121
+ * 3. ALERT ESCALATION REPLACEMENT (event)
104
122
  * ─────────────────────────────────────────────────────────────────────────────
105
123
  *
106
- * 30% of critical/emergency "alert triggered" events are REPLACED with a new
107
- * event type "incident created" (not in the events array — hook-only). Includes
108
- * escalation_level (P1/P2), teams_paged, incident_id.
124
+ * PATTERN: 30% of critical/emergency "alert triggered" events are REPLACED
125
+ * with a hook-only "incident created" event (not in the events array). The
126
+ * new event carries escalation_level (P1/P2), teams_paged, and incident_id.
109
127
  *
110
- * Mixpanel Report Incident Created Discovery:
111
- * • Insights report
112
- * • Event: "incident created"
113
- * • Breakdown: "escalation_level"
114
- * • Expected: P1 and P2 incidents, ~30% of critical/emergency alert volume
128
+ * HOW TO FIND IT IN MIXPANEL:
115
129
  *
116
- * Mixpanel Report Alert vs Incident Ratio:
117
- * Insights report
118
- * Events: "alert triggered" AND "incident created"
119
- * Expected: incident created count ~ 30% of critical+emergency alerts
130
+ * Report 1: Incident Created Discovery
131
+ * - Report type: Insights
132
+ * - Event: "incident created"
133
+ * - Measure: Total
134
+ * - Breakdown: "escalation_level"
135
+ * - Expected: P1 and P2 incidents, ~30% of critical/emergency alert volume
136
+ *
137
+ * Report 2: Alert vs Incident Ratio
138
+ * - Report type: Insights
139
+ * - Events: "alert triggered" AND "incident created"
140
+ * - Measure: Total
141
+ * - Expected: incident count ~ 30% of critical+emergency alert count
142
+ *
143
+ * REAL-WORLD ANALOGUE: Severe alerts get auto-promoted into incident
144
+ * tickets that page on-call engineers and trigger customer comms.
120
145
  *
121
146
  * ─────────────────────────────────────────────────────────────────────────────
122
- * 4. INTEGRATION USERS SUCCEED (everything hook)
147
+ * 4. INTEGRATION USERS SUCCEED (everything)
123
148
  * ─────────────────────────────────────────────────────────────────────────────
124
149
  *
125
- * Users with BOTH Slack AND PagerDuty integrations resolve alerts faster:
126
- * response_time_mins reduced 60%, resolution_time_mins reduced 50%.
127
- * Tagged: integrated_team: true.
150
+ * PATTERN: Users with BOTH "slack" AND "pagerduty" "integration configured"
151
+ * events resolve alerts faster: response_time_mins reduced 60%, resolution_time_mins
152
+ * reduced 50%. No flag — derive cohort behaviorally.
153
+ *
154
+ * HOW TO FIND IT IN MIXPANEL:
128
155
  *
129
- * Mixpanel Report Integration Impact on Response Time:
130
- * Insights report
131
- * Event: "alert acknowledged", measure avg "response_time_mins"
132
- * Breakdown: "integrated_team"
133
- * Expected: integrated_team=true ~ 60% lower response time
156
+ * Report 1: Response Time by Integration Cohort
157
+ * - Cohort A: users who configured BOTH slack AND pagerduty integrations
158
+ * - Cohort B: rest
159
+ * - Event: "alert acknowledged"
160
+ * - Measure: Average of "response_time_mins"
161
+ * - Expected: A ~ 60% lower response time
134
162
  *
135
- * Mixpanel Report Integration Impact on Resolution:
136
- * Insights report
137
- * Event: "alert resolved", measure avg "resolution_time_mins"
138
- * Breakdown: "integrated_team"
139
- * Expected: integrated_team=true ~ 50% faster resolution
163
+ * Report 2: Resolution Time by Integration Cohort
164
+ * - Cohort A vs B (as above)
165
+ * - Event: "alert resolved"
166
+ * - Measure: Average of "resolution_time_mins"
167
+ * - Expected: A ~ 50% faster resolution
168
+ *
169
+ * REAL-WORLD ANALOGUE: Teams that wire alerting into their existing comms
170
+ * stack respond minutes faster — the alert literally finds the human.
140
171
  *
141
172
  * ─────────────────────────────────────────────────────────────────────────────
142
- * 5. DOCS READERS DEPLOY MORE (everything hook)
173
+ * 5. DOCS READERS DEPLOY MORE (everything)
143
174
  * ─────────────────────────────────────────────────────────────────────────────
144
175
  *
145
- * Users with 3+ "best_practices" documentation views get 2-3 extra production
146
- * deploys spliced into their event stream. Tagged: docs_informed: true.
176
+ * PATTERN: Users with 3+ "documentation viewed" events where doc_section
177
+ * (or equivalent prop) indicates best-practices reading get 2-3 extra
178
+ * production deploys spliced in. No flag — derive cohort by counting
179
+ * doc views per user.
180
+ *
181
+ * HOW TO FIND IT IN MIXPANEL:
147
182
  *
148
- * Mixpanel Report Docs-Informed Deployments:
149
- * Insights report
150
- * Event: "service deployed", filter "environment" = "production"
151
- * Breakdown: "docs_informed"
152
- * Expected: docs_informed=true shows extra production deployments
183
+ * Report 1: Per-User Deploy Volume by Docs Cohort
184
+ * - Cohort A: users with >= 3 "documentation viewed" events
185
+ * - Cohort B: users with < 3
186
+ * - Event: "service deployed"
187
+ * - Measure: Total per user
188
+ * - Expected: A ~ 1.8x B
153
189
  *
154
- * Mixpanel Report Docs Readers vs Non-Readers:
155
- * Insights report
156
- * • Event: "service deployed", measure total per user
157
- * • Segment: Users with 3+ "documentation viewed" (doc_section = "best_practices")
158
- * • Expected: ~1.8x more production deploys per user for docs readers
190
+ * REAL-WORLD ANALOGUE: Engineers who read the docs ship more confidently
191
+ * and more often than those who guess at the platform.
159
192
  *
160
193
  * ─────────────────────────────────────────────────────────────────────────────
161
- * 6. COST OVERRUN PATTERN (event hook — closure state)
194
+ * 6. COST OVERRUN PATTERN (event — closure state)
162
195
  * ─────────────────────────────────────────────────────────────────────────────
163
196
  *
164
- * When cost_change_percent > 25 on a "cost report generated" event, the user
165
- * is stored in a module-level Map. Their next "infrastructure scaled" event
166
- * is forced to scale_direction: "down". Tagged: budget_exceeded, cost_reaction.
197
+ * PATTERN: When cost_change_percent > 25 on a "cost report generated" event,
198
+ * the user is stored in a module-level Map. Their next "infrastructure scaled"
199
+ * event is forced to scale_direction = "down". No flag — discover by
200
+ * sequencing cost-report → infrastructure-scaled per user.
201
+ *
202
+ * HOW TO FIND IT IN MIXPANEL:
203
+ *
204
+ * Report 1: Scale Direction Distribution
205
+ * - Report type: Insights
206
+ * - Event: "infrastructure scaled"
207
+ * - Measure: Total
208
+ * - Breakdown: "scale_direction"
209
+ * - Expected: "down" share is elevated above the configured baseline
167
210
  *
168
- * Mixpanel Report Cost Overrun to Scale Down:
169
- * Insights report
170
- * Event: "infrastructure scaled"
171
- * Breakdown: "cost_reaction"
172
- * • Expected: cost_reaction=true events are 100% scale_direction="down"
211
+ * Report 2: Sequencing Check
212
+ * - Inspect users with cost_change_percent > 25 on cost report;
213
+ * their next "infrastructure scaled" should be scale_direction="down"
214
+ * - Expected: ~100% match for the next-scale event after a cost spike
215
+ *
216
+ * REAL-WORLD ANALOGUE: A surprise cloud bill triggers an immediate
217
+ * downscale; no engineer ignores a 25% month-over-month cost jump.
173
218
  *
174
219
  * ─────────────────────────────────────────────────────────────────────────────
175
- * 7. FAILED DEPLOYMENT RECOVERY (event hook — closure state)
220
+ * 7. FAILED DEPLOYMENT RECOVERY (event — closure state)
176
221
  * ─────────────────────────────────────────────────────────────────────────────
177
222
  *
178
- * After a failed pipeline run, the user's next successful deploy has
179
- * duration_sec * 1.5 (recovery deploys are slower). Tagged: recovery_deployment.
180
- * Uses module-level Map for cross-call state.
223
+ * PATTERN: After a failed pipeline run, the user's next successful deploy has
224
+ * duration_sec * 1.5 (recovery deploys are slower). Uses a module-level Map
225
+ * for cross-call state. No flag — discover by sequencing failed → next-success
226
+ * pipeline events per user and comparing duration.
227
+ *
228
+ * HOW TO FIND IT IN MIXPANEL:
229
+ *
230
+ * Report 1: Pipeline Duration After Failure (sequencing query)
231
+ * - For each user, find runs where prior run was status="failed"
232
+ * - Compare avg duration_sec of those "next" runs vs all other successful runs
233
+ * - Expected: post-failure runs ~ 1.5x longer duration
181
234
  *
182
- * Mixpanel Report Recovery Deploy Duration:
183
- * Insights report
184
- * • Event: "deployment pipeline run", measure avg "duration_sec"
185
- * • Breakdown: "recovery_deployment"
186
- * • Expected: recovery_deployment=true ~ 1.5x longer duration
235
+ * REAL-WORLD ANALOGUE: After a bad deploy, teams add manual gates and
236
+ * extra verification steps that slow the very next release.
187
237
  *
188
238
  * ─────────────────────────────────────────────────────────────────────────────
189
- * 8. ENTERPRISE VS STARTUP (user hook)
239
+ * 8. ENTERPRISE VS STARTUP (user)
190
240
  * ─────────────────────────────────────────────────────────────────────────────
191
241
  *
192
- * Company size determines seat_count, annual_contract_value, and
193
- * customer_success_manager (enterprise only). All users get customer_health_score.
242
+ * PATTERN: Company size determines seat_count, annual_contract_value, and
243
+ * customer_success_manager (enterprise only). All users get a
244
+ * customer_health_score on the profile.
194
245
  *
195
- * Mixpanel Report ACV by Company Size:
196
- * • Insights report
197
- * Event: Any, measure unique users
198
- * Breakdown: User profile "company_size"
199
- * Expected: startup ($0-3.6K), smb ($3.6K-12K), mid_market ($12K-50K),
246
+ * HOW TO FIND IT IN MIXPANEL:
247
+ *
248
+ * Report 1: ACV by Company Size
249
+ * - Report type: Insights
250
+ * - Event: any event
251
+ * - Measure: Unique users
252
+ * - Breakdown: "company_size" (user property)
253
+ * - Expected: startup ($0-3.6K), smb ($3.6K-12K), mid_market ($12K-50K),
200
254
  * enterprise ($50K-500K)
201
255
  *
256
+ * Report 2: Seat Count by Company Size
257
+ * - Report type: Insights
258
+ * - Event: any event
259
+ * - Measure: Average of "seat_count" (user property)
260
+ * - Breakdown: "company_size"
261
+ * - Expected: monotonic ramp from startup to enterprise
262
+ *
263
+ * REAL-WORLD ANALOGUE: B2B SaaS pricing scales orders of magnitude across
264
+ * customer segments — from a $99/mo startup to a $500K Fortune 500 contract.
265
+ *
202
266
  * ─────────────────────────────────────────────────────────────────────────────
203
267
  * EXPECTED METRICS SUMMARY
204
268
  * ─────────────────────────────────────────────────────────────────────────────
@@ -257,7 +321,9 @@ const failedDeployUsers = new Map();
257
321
  const config = {
258
322
  token,
259
323
  seed: SEED,
260
- numDays: num_days,
324
+ datasetStart: "2026-01-01T00:00:00Z",
325
+ datasetEnd: "2026-04-28T23:59:59Z",
326
+ // numDays: num_days,
261
327
  avgEventsPerUserPerDay: avg_events_per_user_per_day,
262
328
  numUsers: num_users,
263
329
  hasAnonIds: false,
@@ -368,7 +434,6 @@ const config = {
368
434
  service_type: ["web_app", "api", "database", "cache", "queue", "ml_model"],
369
435
  environment: ["production", "staging", "dev"],
370
436
  cloud_provider: ["aws", "gcp", "azure"],
371
- docs_informed: [false],
372
437
  }
373
438
  },
374
439
  {
@@ -409,7 +474,6 @@ const config = {
409
474
  alert_id: alertIds,
410
475
  response_time_mins: u.weighNumRange(1, 120),
411
476
  acknowledged_by_role: ["engineer", "sre", "manager", "oncall"],
412
- integrated_team: [false],
413
477
  }
414
478
  },
415
479
  {
@@ -419,7 +483,6 @@ const config = {
419
483
  alert_id: alertIds,
420
484
  resolution_time_mins: u.weighNumRange(5, 1440),
421
485
  root_cause: ["config_change", "capacity", "bug", "dependency", "network"],
422
- integrated_team: [false],
423
486
  }
424
487
  },
425
488
  {
@@ -430,7 +493,6 @@ const config = {
430
493
  status: ["success", "failed", "cancelled"],
431
494
  duration_sec: u.weighNumRange(30, 1800),
432
495
  commit_count: u.weighNumRange(1, 20),
433
- recovery_deployment: [false],
434
496
  }
435
497
  },
436
498
  {
@@ -442,7 +504,6 @@ const config = {
442
504
  previous_capacity: u.weighNumRange(1, 100),
443
505
  new_capacity: u.weighNumRange(1, 100),
444
506
  auto_scaled: [false, false, false, false, false, false, true],
445
- cost_reaction: [false],
446
507
  }
447
508
  },
448
509
  {
@@ -452,8 +513,6 @@ const config = {
452
513
  report_period: ["daily", "weekly", "monthly"],
453
514
  total_cost: u.weighNumRange(100, 50000),
454
515
  cost_change_percent: u.weighNumRange(-30, 50),
455
- cost_alert: [false],
456
- budget_exceeded: [false],
457
516
  }
458
517
  },
459
518
  {
@@ -462,8 +521,6 @@ const config = {
462
521
  properties: {
463
522
  role: ["admin", "editor", "viewer", "billing"],
464
523
  invitation_method: ["email", "sso", "slack"],
465
- quarter_end_push: [false],
466
- duplicate_invite: [false],
467
524
  }
468
525
  },
469
526
  {
@@ -498,7 +555,6 @@ const config = {
498
555
  properties: {
499
556
  event_type: ["invoice_generated", "payment_received", "payment_failed", "plan_upgraded", "plan_downgraded"],
500
557
  amount: u.weighNumRange(99, 25000),
501
- quarter_end_push: [false],
502
558
  }
503
559
  },
504
560
  {
@@ -548,7 +604,6 @@ const config = {
548
604
  company_size: ["startup", "startup", "smb", "mid_market", "enterprise"],
549
605
  primary_role: ["engineer", "sre", "devops", "manager", "executive"],
550
606
  team_name: ["Platform", "Backend", "Frontend", "Data", "Security", "Infrastructure"],
551
- churned_account: [false],
552
607
  seat_count: [1],
553
608
  annual_contract_value: [0],
554
609
  customer_success_manager: [false],
@@ -587,55 +642,36 @@ const config = {
587
642
  * 8. ENTERPRISE VS STARTUP: Company size determines seat count, ACV, and health score
588
643
  */
589
644
  hook: function (record, type, meta) {
590
- const NOW = dayjs();
591
- const DATASET_START = NOW.subtract(num_days, "days");
592
-
593
- // ─────────────────────────────────────────────────────────────
594
- // Hook #1: END-OF-QUARTER SPIKE (event)
595
- // Days 80-90: billing upgrades and team expansion surge
596
- // ─────────────────────────────────────────────────────────────
645
+ // HOOK 1: END-OF-QUARTER SPIKE — days 80-90, 40% of billing events
646
+ // flip event_type to plan_upgraded; team-member-invited events get
647
+ // 50% chance of duplicate clone with unique time. No flag.
597
648
  if (type === "event") {
649
+ const datasetStart = dayjs.unix(meta.datasetStart);
598
650
  const EVENT_TIME = dayjs(record.time);
599
- const dayInDataset = EVENT_TIME.diff(DATASET_START, "days", true);
651
+ const dayInDataset = EVENT_TIME.diff(datasetStart, "days", true);
600
652
 
601
653
  if (record.event === "billing event") {
602
654
  if (dayInDataset >= 80 && dayInDataset <= 90 && chance.bool({ likelihood: 40 })) {
603
655
  record.event_type = "plan_upgraded";
604
- record.quarter_end_push = true;
605
- } else {
606
- record.quarter_end_push = false;
607
656
  }
608
657
  }
609
658
 
610
- if (record.event === "team member invited") {
611
- if (dayInDataset >= 80 && dayInDataset <= 90) {
612
- record.quarter_end_push = true;
613
- // 50% of the time duplicate the invite event (hiring push)
614
- if (chance.bool({ likelihood: 50 })) {
615
- return {
616
- ...record,
617
- time: EVENT_TIME.add(chance.integer({ min: 1, max: 60 }), "minutes").toISOString(),
618
- role: chance.pickone(["editor", "viewer"]),
619
- invitation_method: chance.pickone(["email", "sso", "slack"]),
620
- quarter_end_push: true,
621
- duplicate_invite: true,
622
- };
623
- }
624
- } else {
625
- record.quarter_end_push = false;
626
- }
659
+ if (record.event === "team member invited" && dayInDataset >= 80 && dayInDataset <= 90 && chance.bool({ likelihood: 50 })) {
660
+ return {
661
+ ...record,
662
+ time: EVENT_TIME.add(chance.integer({ min: 1, max: 60 }), "minutes").toISOString(),
663
+ role: chance.pickone(["editor", "viewer"]),
664
+ invitation_method: chance.pickone(["email", "sso", "slack"]),
665
+ };
627
666
  }
628
667
  }
629
668
 
630
- // ─────────────────────────────────────────────────────────────
631
- // Hook #3: ALERT ESCALATION REPLACEMENT (event)
632
- // Critical/emergency alerts sometimes become formal incidents
633
- // ─────────────────────────────────────────────────────────────
669
+ // HOOK 3: ALERT ESCALATION REPLACEMENT (event) — critical/emergency
670
+ // alerts sometimes become incident-created events. Real product flow.
634
671
  if (type === "event") {
635
672
  if (record.event === "alert triggered") {
636
673
  const severity = record.severity;
637
674
  if ((severity === "critical" || severity === "emergency") && chance.bool({ likelihood: 30 })) {
638
- // REPLACE the event entirely with an "incident created" event
639
675
  return {
640
676
  ...record,
641
677
  event: "incident created",
@@ -650,178 +686,133 @@ const config = {
650
686
  }
651
687
  }
652
688
 
653
- // ─────────────────────────────────────────────────────────────
654
- // Hook #6: COST OVERRUN PATTERN (event)
655
- // Budget-exceeded users react by scaling down infrastructure
656
- // Uses module-level costOverrunUsers Map for cross-call state
657
- // ─────────────────────────────────────────────────────────────
689
+ // HOOK 6: COST OVERRUN PATTERN (event) — cost reports with cost_change
690
+ // > 25% record user, then next infrastructure-scaled event from that
691
+ // user gets scale_direction = "down". No flag.
658
692
  if (type === "event") {
659
- if (record.event === "cost report generated") {
660
- const costChange = record.cost_change_percent;
661
- if (costChange > 25) {
662
- record.cost_alert = true;
663
- record.budget_exceeded = true;
664
- costOverrunUsers.set(record.user_id, true);
665
- } else {
666
- record.cost_alert = false;
667
- record.budget_exceeded = false;
668
- }
693
+ if (record.event === "cost report generated" && record.cost_change_percent > 25) {
694
+ costOverrunUsers.set(record.user_id, true);
669
695
  }
670
-
671
- if (record.event === "infrastructure scaled") {
672
- if (costOverrunUsers.has(record.user_id)) {
673
- record.scale_direction = "down";
674
- record.cost_reaction = true;
675
- costOverrunUsers.delete(record.user_id);
676
- } else {
677
- record.cost_reaction = false;
678
- }
696
+ if (record.event === "infrastructure scaled" && costOverrunUsers.has(record.user_id)) {
697
+ record.scale_direction = "down";
698
+ costOverrunUsers.delete(record.user_id);
679
699
  }
680
700
  }
681
701
 
682
- // ─────────────────────────────────────────────────────────────
683
- // Hook #7: FAILED DEPLOYMENT RECOVERY (event)
684
- // Recovery deploys take 1.5x longer after a failure
685
- // Uses module-level failedDeployUsers Map for cross-call state
686
- // ─────────────────────────────────────────────────────────────
702
+ // HOOK 7: FAILED DEPLOYMENT RECOVERY (event) — failed deploy records
703
+ // user, then next successful deploy gets duration_sec * 1.5. No flag.
687
704
  if (type === "event") {
688
705
  if (record.event === "deployment pipeline run") {
689
- const status = record.status;
690
- if (status === "failed") {
706
+ if (record.status === "failed") {
691
707
  failedDeployUsers.set(record.user_id, true);
692
- record.recovery_deployment = false;
693
- } else if (status === "success" && failedDeployUsers.has(record.user_id)) {
708
+ } else if (record.status === "success" && failedDeployUsers.has(record.user_id)) {
694
709
  record.duration_sec = Math.floor((record.duration_sec || 300) * 1.5);
695
- record.recovery_deployment = true;
696
710
  failedDeployUsers.delete(record.user_id);
697
- } else {
698
- record.recovery_deployment = false;
699
711
  }
700
712
  }
701
713
  }
702
714
 
703
- // ─────────────────────────────────────────────────────────────
704
- // Hook #2: CHURNED ACCOUNT SILENCING (everything)
705
- // ~20% targeted (hash % 5), yielding ~10% visible after accounting for invisible churned users
706
- // ─────────────────────────────────────────────────────────────
715
+ // HOOK 9 (T2C): INCIDENT RESPONSE TIME-TO-CONVERT (funnel-post)
716
+ // Enterprise tier completes Incident Response funnel 1.4x faster
717
+ // (factor 0.71); Startup 1.25x slower (factor 1.25).
718
+ if (type === "funnel-post") {
719
+ const segment = meta?.profile?.company_size;
720
+ if (Array.isArray(record) && record.length > 1) {
721
+ const factor = (
722
+ segment === "enterprise" ? 0.71 :
723
+ segment === "startup" ? 1.25 :
724
+ 1.0
725
+ );
726
+ if (factor !== 1.0) {
727
+ for (let i = 1; i < record.length; i++) {
728
+ const prev = dayjs(record[i - 1].time);
729
+ const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
730
+ record[i].time = prev.add(newGap, "milliseconds").toISOString();
731
+ }
732
+ }
733
+ }
734
+ }
735
+
707
736
  if (type === "everything") {
737
+ const datasetStart = dayjs.unix(meta.datasetStart);
708
738
  const userEvents = record;
709
739
  const profile = meta.profile;
710
740
 
711
- // Stamp superProps from profile for consistency
712
741
  userEvents.forEach(e => {
713
742
  e.plan_tier = profile.plan_tier;
714
743
  e.cloud_provider = profile.cloud_provider;
715
744
  });
716
745
 
746
+ // HOOK 2: CHURNED ACCOUNT SILENCING — ~20% of users (hash %5)
747
+ // have post-day-30 events removed. No flag.
717
748
  if (userEvents && userEvents.length > 0) {
718
749
  const firstEvent = userEvents[0];
719
750
  const idHash = String(firstEvent.user_id || firstEvent.device_id).split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
720
- const isChurnedAccount = (idHash % 5) === 0;
721
-
722
- if (isChurnedAccount) {
751
+ if ((idHash % 5) === 0) {
723
752
  for (let i = userEvents.length - 1; i >= 0; i--) {
724
- const evt = userEvents[i];
725
- const dayInDataset = dayjs(evt.time).diff(DATASET_START, "days", true);
753
+ const dayInDataset = dayjs(userEvents[i].time).diff(datasetStart, "days", true);
726
754
  if (dayInDataset > 30) {
727
755
  userEvents.splice(i, 1);
728
756
  }
729
757
  }
730
758
  }
731
759
  }
732
- }
733
760
 
734
- // ─────────────────────────────────────────────────────────────
735
- // Hook #4: INTEGRATION USERS SUCCEED (everything)
736
- // Users with both Slack AND PagerDuty integrations resolve faster
737
- // ─────────────────────────────────────────────────────────────
738
- if (type === "everything") {
739
- const userEvents = record;
740
-
741
- // First pass: check if user has both slack and pagerduty integrations
761
+ // HOOK 4: INTEGRATION USERS SUCCEED — Slack+PagerDuty users get
762
+ // alert response_time_mins 0.4x and resolution_time_mins 0.5x.
763
+ // Mutates raw props. No flag.
742
764
  let hasSlack = false;
743
765
  let hasPagerduty = false;
744
-
745
766
  userEvents.forEach((event) => {
746
767
  if (event.event === "integration configured") {
747
- const integrationType = event.integration_type;
748
- if (integrationType === "slack") hasSlack = true;
749
- if (integrationType === "pagerduty") hasPagerduty = true;
768
+ if (event.integration_type === "slack") hasSlack = true;
769
+ if (event.integration_type === "pagerduty") hasPagerduty = true;
750
770
  }
751
771
  });
752
-
753
- const hasFullIntegration = hasSlack && hasPagerduty;
754
-
755
- // Second pass: set integrated_team on all alert events, then boost for integrated users
756
- userEvents.forEach((event) => {
757
- if (event.event === "alert acknowledged") {
758
- if (hasFullIntegration && event.response_time_mins) {
772
+ if (hasSlack && hasPagerduty) {
773
+ userEvents.forEach((event) => {
774
+ if (event.event === "alert acknowledged" && event.response_time_mins) {
759
775
  event.response_time_mins = Math.floor(event.response_time_mins * 0.4);
760
- event.integrated_team = true;
761
- } else {
762
- event.integrated_team = false;
763
776
  }
764
- }
765
- if (event.event === "alert resolved") {
766
- if (hasFullIntegration && event.resolution_time_mins) {
777
+ if (event.event === "alert resolved" && event.resolution_time_mins) {
767
778
  event.resolution_time_mins = Math.floor(event.resolution_time_mins * 0.5);
768
- event.integrated_team = true;
769
- } else {
770
- event.integrated_team = false;
771
779
  }
772
- }
773
- });
774
- }
775
-
776
- // ─────────────────────────────────────────────────────────────
777
- // Hook #5: DOCS READERS DEPLOY MORE (everything)
778
- // Users who read best_practices 3+ times get extra production deploys
779
- // ─────────────────────────────────────────────────────────────
780
- if (type === "everything") {
781
- const userEvents = record;
782
-
783
- // First pass: count best_practices documentation views
784
- let bestPracticesCount = 0;
785
- userEvents.forEach((event) => {
786
- if (event.event === "documentation viewed" && event.doc_section === "best_practices") {
787
- bestPracticesCount++;
788
- }
789
- });
780
+ });
781
+ }
790
782
 
791
- // Second pass: if 3+ best practices views, add extra production deploys
792
- if (bestPracticesCount >= 3) {
793
- const extraDeploys = chance.integer({ min: 2, max: 3 });
783
+ // HOOK 5 + HOOK 10: DOCS MAGIC NUMBER (in-funnel, no flags)
784
+ // Sweet 4-7 documentation-viewed events → +40% extra cloned
785
+ // service-deployed events. Over 8+ drop 25% of service-deployed
786
+ // events (over-reading; no shipping). No flag.
787
+ const docsCount = userEvents.filter(e => e.event === "documentation viewed").length;
788
+ const deployTemplate = userEvents.find(e => e.event === "service deployed");
789
+ if (docsCount >= 4 && docsCount <= 7 && deployTemplate) {
794
790
  const lastEvent = userEvents[userEvents.length - 1];
795
- const deployTemplate = userEvents.find(e => e.event === "service deployed");
796
- if (lastEvent && deployTemplate) {
797
- for (let i = 0; i < extraDeploys; i++) {
798
- const deployEvent = {
799
- ...deployTemplate,
800
- time: dayjs(lastEvent.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
801
- user_id: lastEvent.user_id,
802
- service_id: chance.pickone(serviceIds),
803
- service_type: chance.pickone(["web_app", "api", "database", "cache", "queue", "ml_model"]),
804
- environment: "production",
805
- cloud_provider: chance.pickone(["aws", "gcp", "azure"]),
806
- docs_informed: true,
807
- };
808
- userEvents.splice(userEvents.length, 0, deployEvent);
791
+ const extraDeploys = chance.integer({ min: 2, max: 3 });
792
+ for (let i = 0; i < extraDeploys; i++) {
793
+ userEvents.push({
794
+ ...deployTemplate,
795
+ time: dayjs(lastEvent.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
796
+ user_id: lastEvent.user_id,
797
+ service_id: chance.pickone(serviceIds),
798
+ service_type: chance.pickone(["web_app", "api", "database", "cache", "queue", "ml_model"]),
799
+ environment: "production",
800
+ cloud_provider: profile.cloud_provider,
801
+ });
802
+ }
803
+ } else if (docsCount >= 8) {
804
+ for (let i = userEvents.length - 1; i >= 0; i--) {
805
+ if (userEvents[i].event === "service deployed" && chance.bool({ likelihood: 25 })) {
806
+ userEvents.splice(i, 1);
809
807
  }
810
808
  }
811
809
  }
812
810
  }
813
811
 
814
- // ─────────────────────────────────────────────────────────────
815
- // Hook #8: ENTERPRISE VS STARTUP (user)
816
- // Company size determines seat count, ACV, and health score
817
- // ─────────────────────────────────────────────────────────────
812
+ // HOOK 8: ENTERPRISE VS STARTUP (user) — company size determines
813
+ // seat count, ACV, and CSM. Real profile attrs.
818
814
  if (type === "user") {
819
- // Hook #2 support: tag churned accounts on user profile for discoverability
820
- const idHash = String(record.distinct_id || "").split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
821
- record.churned_account = (idHash % 5) === 0;
822
-
823
815
  const companySize = record.company_size;
824
-
825
816
  if (companySize === "enterprise") {
826
817
  record.seat_count = chance.integer({ min: 50, max: 500 });
827
818
  record.annual_contract_value = chance.integer({ min: 50000, max: 500000 });
@@ -839,7 +830,6 @@ const config = {
839
830
  record.annual_contract_value = chance.integer({ min: 0, max: 3600 });
840
831
  record.customer_success_manager = false;
841
832
  }
842
-
843
833
  record.customer_health_score = chance.integer({ min: 1, max: 100 });
844
834
  }
845
835