@ak--47/dungeon-master 1.2.3 → 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 (89) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +51 -13
  3. package/dungeons/technical/ad-spend.js +2 -2
  4. package/dungeons/technical/anonymous-users.js +2 -2
  5. package/dungeons/technical/array-of-object-lookup.js +2 -4
  6. package/dungeons/technical/experiments.js +2 -2
  7. package/dungeons/technical/foobar.js +2 -2
  8. package/dungeons/technical/group-analytics.js +2 -2
  9. package/dungeons/technical/mirror-strategies.js +2 -2
  10. package/dungeons/technical/nested-objects.js +2 -2
  11. package/dungeons/technical/retention-cadence.js +2 -3
  12. package/dungeons/technical/sanity.js +2 -2
  13. package/dungeons/technical/scale-test.js +2 -2
  14. package/dungeons/technical/scd.js +2 -2
  15. package/dungeons/technical/simple.js +5 -6
  16. package/dungeons/technical/simplest-schema.json +5 -0
  17. package/dungeons/technical/simplest.js +2 -2
  18. package/dungeons/technical/text-generation.js +3 -3
  19. package/dungeons/vertical/ai-platform.js +858 -0
  20. package/dungeons/vertical/community.js +84 -40
  21. package/dungeons/vertical/crypto.js +830 -0
  22. package/dungeons/vertical/dating.js +744 -0
  23. package/dungeons/vertical/devtools.js +175 -69
  24. package/dungeons/vertical/ecommerce.js +242 -94
  25. package/dungeons/vertical/education.js +330 -313
  26. package/dungeons/vertical/fintech.js +442 -313
  27. package/dungeons/vertical/fitness.js +143 -61
  28. package/dungeons/vertical/food-delivery.js +327 -353
  29. package/dungeons/vertical/gaming.js +912 -382
  30. package/dungeons/vertical/healthcare.js +142 -63
  31. package/dungeons/vertical/insurance-application.js +170 -76
  32. package/dungeons/vertical/logistics.js +115 -20
  33. package/dungeons/vertical/marketplace.js +152 -58
  34. package/dungeons/vertical/media.js +248 -384
  35. package/dungeons/vertical/real-estate.js +781 -0
  36. package/dungeons/vertical/sass.js +255 -266
  37. package/dungeons/vertical/social.js +264 -206
  38. package/dungeons/vertical/travel.js +117 -41
  39. package/index.js +17 -17
  40. package/lib/core/config-validator.js +159 -31
  41. package/lib/core/context.js +10 -24
  42. package/lib/core/storage.js +6 -1
  43. package/lib/generators/events.js +11 -14
  44. package/lib/generators/funnels.js +12 -4
  45. package/lib/generators/mirror.js +3 -2
  46. package/lib/generators/product-names.js +1 -1
  47. package/lib/generators/scd.js +2 -1
  48. package/lib/generators/text.js +1 -1
  49. package/lib/orchestrators/user-loop.js +81 -47
  50. package/lib/templates/macro-presets.js +111 -0
  51. package/lib/templates/soup-presets.js +19 -36
  52. package/lib/utils/utils.js +71 -39
  53. package/package.json +8 -2
  54. package/scripts/smoke-test-all.mjs +162 -0
  55. package/scripts/verify-runner.mjs +72 -24
  56. package/types.d.ts +251 -51
  57. package/dungeons/technical/ad-spend-schema.json +0 -128
  58. package/dungeons/technical/anonymous-users-schema.json +0 -92
  59. package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
  60. package/dungeons/technical/experiments-schema.json +0 -203
  61. package/dungeons/technical/foobar-schema.json +0 -362
  62. package/dungeons/technical/group-analytics-schema.json +0 -241
  63. package/dungeons/technical/mirror-strategies-schema.json +0 -84
  64. package/dungeons/technical/nested-objects-schema.json +0 -145
  65. package/dungeons/technical/retention-cadence-schema.json +0 -37
  66. package/dungeons/technical/sanity-schema.json +0 -185
  67. package/dungeons/technical/scale-test-schema.json +0 -70
  68. package/dungeons/technical/scd-schema.json +0 -467
  69. package/dungeons/technical/simple-schema.json +0 -362
  70. package/dungeons/technical/text-generation-schema.json +0 -1062
  71. package/dungeons/user/.gitkeep +0 -0
  72. package/dungeons/vertical/community-schema.json +0 -579
  73. package/dungeons/vertical/devtools-schema.json +0 -601
  74. package/dungeons/vertical/ecommerce-schema.json +0 -604
  75. package/dungeons/vertical/education-schema.json +0 -5686
  76. package/dungeons/vertical/fintech-schema.json +0 -630
  77. package/dungeons/vertical/fitness-schema.json +0 -530
  78. package/dungeons/vertical/food-delivery-schema.json +0 -36728
  79. package/dungeons/vertical/gaming-schema.json +0 -438
  80. package/dungeons/vertical/healthcare-schema.json +0 -549
  81. package/dungeons/vertical/insurance-application-schema.json +0 -485
  82. package/dungeons/vertical/logistics-schema.json +0 -574
  83. package/dungeons/vertical/marketplace-schema.json +0 -533
  84. package/dungeons/vertical/media-schema.json +0 -4749
  85. package/dungeons/vertical/rpg-schema.json +0 -2491
  86. package/dungeons/vertical/rpg.js +0 -976
  87. package/dungeons/vertical/sass-schema.json +0 -3128
  88. package/dungeons/vertical/social-schema.json +0 -620
  89. package/dungeons/vertical/travel-schema.json +0 -580
@@ -2,7 +2,7 @@
2
2
  const SEED = "harness-sass";
3
3
  const num_days = 100;
4
4
  const num_users = 5_000;
5
- const avg_events_per_user = 120;
5
+ const avg_events_per_user_per_day = 1.2;
6
6
  let token = "your-mixpanel-token";
7
7
 
8
8
  // ── env overrides ──
@@ -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,8 +321,10 @@ const failedDeployUsers = new Map();
257
321
  const config = {
258
322
  token,
259
323
  seed: SEED,
260
- numDays: num_days,
261
- numEvents: num_users * avg_events_per_user,
324
+ datasetStart: "2026-01-01T00:00:00Z",
325
+ datasetEnd: "2026-04-28T23:59:59Z",
326
+ // numDays: num_days,
327
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
262
328
  numUsers: num_users,
263
329
  hasAnonIds: false,
264
330
  hasSessionIds: true,
@@ -273,7 +339,6 @@ const config = {
273
339
  hasCampaigns: false,
274
340
  isAnonymous: false,
275
341
  hasAdSpend: false,
276
- percentUsersBornInDataset: 50,
277
342
  hasAvatar: true,
278
343
  concurrency: 1,
279
344
  writeToDisk: false,
@@ -369,7 +434,6 @@ const config = {
369
434
  service_type: ["web_app", "api", "database", "cache", "queue", "ml_model"],
370
435
  environment: ["production", "staging", "dev"],
371
436
  cloud_provider: ["aws", "gcp", "azure"],
372
- docs_informed: [false],
373
437
  }
374
438
  },
375
439
  {
@@ -410,7 +474,6 @@ const config = {
410
474
  alert_id: alertIds,
411
475
  response_time_mins: u.weighNumRange(1, 120),
412
476
  acknowledged_by_role: ["engineer", "sre", "manager", "oncall"],
413
- integrated_team: [false],
414
477
  }
415
478
  },
416
479
  {
@@ -420,7 +483,6 @@ const config = {
420
483
  alert_id: alertIds,
421
484
  resolution_time_mins: u.weighNumRange(5, 1440),
422
485
  root_cause: ["config_change", "capacity", "bug", "dependency", "network"],
423
- integrated_team: [false],
424
486
  }
425
487
  },
426
488
  {
@@ -431,7 +493,6 @@ const config = {
431
493
  status: ["success", "failed", "cancelled"],
432
494
  duration_sec: u.weighNumRange(30, 1800),
433
495
  commit_count: u.weighNumRange(1, 20),
434
- recovery_deployment: [false],
435
496
  }
436
497
  },
437
498
  {
@@ -443,7 +504,6 @@ const config = {
443
504
  previous_capacity: u.weighNumRange(1, 100),
444
505
  new_capacity: u.weighNumRange(1, 100),
445
506
  auto_scaled: [false, false, false, false, false, false, true],
446
- cost_reaction: [false],
447
507
  }
448
508
  },
449
509
  {
@@ -453,8 +513,6 @@ const config = {
453
513
  report_period: ["daily", "weekly", "monthly"],
454
514
  total_cost: u.weighNumRange(100, 50000),
455
515
  cost_change_percent: u.weighNumRange(-30, 50),
456
- cost_alert: [false],
457
- budget_exceeded: [false],
458
516
  }
459
517
  },
460
518
  {
@@ -463,8 +521,6 @@ const config = {
463
521
  properties: {
464
522
  role: ["admin", "editor", "viewer", "billing"],
465
523
  invitation_method: ["email", "sso", "slack"],
466
- quarter_end_push: [false],
467
- duplicate_invite: [false],
468
524
  }
469
525
  },
470
526
  {
@@ -499,7 +555,6 @@ const config = {
499
555
  properties: {
500
556
  event_type: ["invoice_generated", "payment_received", "payment_failed", "plan_upgraded", "plan_downgraded"],
501
557
  amount: u.weighNumRange(99, 25000),
502
- quarter_end_push: [false],
503
558
  }
504
559
  },
505
560
  {
@@ -549,7 +604,6 @@ const config = {
549
604
  company_size: ["startup", "startup", "smb", "mid_market", "enterprise"],
550
605
  primary_role: ["engineer", "sre", "devops", "manager", "executive"],
551
606
  team_name: ["Platform", "Backend", "Frontend", "Data", "Security", "Infrastructure"],
552
- churned_account: [false],
553
607
  seat_count: [1],
554
608
  annual_contract_value: [0],
555
609
  customer_success_manager: [false],
@@ -588,55 +642,36 @@ const config = {
588
642
  * 8. ENTERPRISE VS STARTUP: Company size determines seat count, ACV, and health score
589
643
  */
590
644
  hook: function (record, type, meta) {
591
- const NOW = dayjs();
592
- const DATASET_START = NOW.subtract(num_days, "days");
593
-
594
- // ─────────────────────────────────────────────────────────────
595
- // Hook #1: END-OF-QUARTER SPIKE (event)
596
- // Days 80-90: billing upgrades and team expansion surge
597
- // ─────────────────────────────────────────────────────────────
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.
598
648
  if (type === "event") {
649
+ const datasetStart = dayjs.unix(meta.datasetStart);
599
650
  const EVENT_TIME = dayjs(record.time);
600
- const dayInDataset = EVENT_TIME.diff(DATASET_START, "days", true);
651
+ const dayInDataset = EVENT_TIME.diff(datasetStart, "days", true);
601
652
 
602
653
  if (record.event === "billing event") {
603
654
  if (dayInDataset >= 80 && dayInDataset <= 90 && chance.bool({ likelihood: 40 })) {
604
655
  record.event_type = "plan_upgraded";
605
- record.quarter_end_push = true;
606
- } else {
607
- record.quarter_end_push = false;
608
656
  }
609
657
  }
610
658
 
611
- if (record.event === "team member invited") {
612
- if (dayInDataset >= 80 && dayInDataset <= 90) {
613
- record.quarter_end_push = true;
614
- // 50% of the time duplicate the invite event (hiring push)
615
- if (chance.bool({ likelihood: 50 })) {
616
- return {
617
- ...record,
618
- time: EVENT_TIME.add(chance.integer({ min: 1, max: 60 }), "minutes").toISOString(),
619
- role: chance.pickone(["editor", "viewer"]),
620
- invitation_method: chance.pickone(["email", "sso", "slack"]),
621
- quarter_end_push: true,
622
- duplicate_invite: true,
623
- };
624
- }
625
- } else {
626
- record.quarter_end_push = false;
627
- }
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
+ };
628
666
  }
629
667
  }
630
668
 
631
- // ─────────────────────────────────────────────────────────────
632
- // Hook #3: ALERT ESCALATION REPLACEMENT (event)
633
- // Critical/emergency alerts sometimes become formal incidents
634
- // ─────────────────────────────────────────────────────────────
669
+ // HOOK 3: ALERT ESCALATION REPLACEMENT (event) — critical/emergency
670
+ // alerts sometimes become incident-created events. Real product flow.
635
671
  if (type === "event") {
636
672
  if (record.event === "alert triggered") {
637
673
  const severity = record.severity;
638
674
  if ((severity === "critical" || severity === "emergency") && chance.bool({ likelihood: 30 })) {
639
- // REPLACE the event entirely with an "incident created" event
640
675
  return {
641
676
  ...record,
642
677
  event: "incident created",
@@ -651,178 +686,133 @@ const config = {
651
686
  }
652
687
  }
653
688
 
654
- // ─────────────────────────────────────────────────────────────
655
- // Hook #6: COST OVERRUN PATTERN (event)
656
- // Budget-exceeded users react by scaling down infrastructure
657
- // Uses module-level costOverrunUsers Map for cross-call state
658
- // ─────────────────────────────────────────────────────────────
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.
659
692
  if (type === "event") {
660
- if (record.event === "cost report generated") {
661
- const costChange = record.cost_change_percent;
662
- if (costChange > 25) {
663
- record.cost_alert = true;
664
- record.budget_exceeded = true;
665
- costOverrunUsers.set(record.user_id, true);
666
- } else {
667
- record.cost_alert = false;
668
- record.budget_exceeded = false;
669
- }
693
+ if (record.event === "cost report generated" && record.cost_change_percent > 25) {
694
+ costOverrunUsers.set(record.user_id, true);
670
695
  }
671
-
672
- if (record.event === "infrastructure scaled") {
673
- if (costOverrunUsers.has(record.user_id)) {
674
- record.scale_direction = "down";
675
- record.cost_reaction = true;
676
- costOverrunUsers.delete(record.user_id);
677
- } else {
678
- record.cost_reaction = false;
679
- }
696
+ if (record.event === "infrastructure scaled" && costOverrunUsers.has(record.user_id)) {
697
+ record.scale_direction = "down";
698
+ costOverrunUsers.delete(record.user_id);
680
699
  }
681
700
  }
682
701
 
683
- // ─────────────────────────────────────────────────────────────
684
- // Hook #7: FAILED DEPLOYMENT RECOVERY (event)
685
- // Recovery deploys take 1.5x longer after a failure
686
- // Uses module-level failedDeployUsers Map for cross-call state
687
- // ─────────────────────────────────────────────────────────────
702
+ // HOOK 7: FAILED DEPLOYMENT RECOVERY (event) — failed deploy records
703
+ // user, then next successful deploy gets duration_sec * 1.5. No flag.
688
704
  if (type === "event") {
689
705
  if (record.event === "deployment pipeline run") {
690
- const status = record.status;
691
- if (status === "failed") {
706
+ if (record.status === "failed") {
692
707
  failedDeployUsers.set(record.user_id, true);
693
- record.recovery_deployment = false;
694
- } else if (status === "success" && failedDeployUsers.has(record.user_id)) {
708
+ } else if (record.status === "success" && failedDeployUsers.has(record.user_id)) {
695
709
  record.duration_sec = Math.floor((record.duration_sec || 300) * 1.5);
696
- record.recovery_deployment = true;
697
710
  failedDeployUsers.delete(record.user_id);
698
- } else {
699
- record.recovery_deployment = false;
700
711
  }
701
712
  }
702
713
  }
703
714
 
704
- // ─────────────────────────────────────────────────────────────
705
- // Hook #2: CHURNED ACCOUNT SILENCING (everything)
706
- // ~20% targeted (hash % 5), yielding ~10% visible after accounting for invisible churned users
707
- // ─────────────────────────────────────────────────────────────
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
+
708
736
  if (type === "everything") {
737
+ const datasetStart = dayjs.unix(meta.datasetStart);
709
738
  const userEvents = record;
710
739
  const profile = meta.profile;
711
740
 
712
- // Stamp superProps from profile for consistency
713
741
  userEvents.forEach(e => {
714
742
  e.plan_tier = profile.plan_tier;
715
743
  e.cloud_provider = profile.cloud_provider;
716
744
  });
717
745
 
746
+ // HOOK 2: CHURNED ACCOUNT SILENCING — ~20% of users (hash %5)
747
+ // have post-day-30 events removed. No flag.
718
748
  if (userEvents && userEvents.length > 0) {
719
749
  const firstEvent = userEvents[0];
720
750
  const idHash = String(firstEvent.user_id || firstEvent.device_id).split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
721
- const isChurnedAccount = (idHash % 5) === 0;
722
-
723
- if (isChurnedAccount) {
751
+ if ((idHash % 5) === 0) {
724
752
  for (let i = userEvents.length - 1; i >= 0; i--) {
725
- const evt = userEvents[i];
726
- const dayInDataset = dayjs(evt.time).diff(DATASET_START, "days", true);
753
+ const dayInDataset = dayjs(userEvents[i].time).diff(datasetStart, "days", true);
727
754
  if (dayInDataset > 30) {
728
755
  userEvents.splice(i, 1);
729
756
  }
730
757
  }
731
758
  }
732
759
  }
733
- }
734
760
 
735
- // ─────────────────────────────────────────────────────────────
736
- // Hook #4: INTEGRATION USERS SUCCEED (everything)
737
- // Users with both Slack AND PagerDuty integrations resolve faster
738
- // ─────────────────────────────────────────────────────────────
739
- if (type === "everything") {
740
- const userEvents = record;
741
-
742
- // 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.
743
764
  let hasSlack = false;
744
765
  let hasPagerduty = false;
745
-
746
766
  userEvents.forEach((event) => {
747
767
  if (event.event === "integration configured") {
748
- const integrationType = event.integration_type;
749
- if (integrationType === "slack") hasSlack = true;
750
- if (integrationType === "pagerduty") hasPagerduty = true;
768
+ if (event.integration_type === "slack") hasSlack = true;
769
+ if (event.integration_type === "pagerduty") hasPagerduty = true;
751
770
  }
752
771
  });
753
-
754
- const hasFullIntegration = hasSlack && hasPagerduty;
755
-
756
- // Second pass: set integrated_team on all alert events, then boost for integrated users
757
- userEvents.forEach((event) => {
758
- if (event.event === "alert acknowledged") {
759
- if (hasFullIntegration && event.response_time_mins) {
772
+ if (hasSlack && hasPagerduty) {
773
+ userEvents.forEach((event) => {
774
+ if (event.event === "alert acknowledged" && event.response_time_mins) {
760
775
  event.response_time_mins = Math.floor(event.response_time_mins * 0.4);
761
- event.integrated_team = true;
762
- } else {
763
- event.integrated_team = false;
764
776
  }
765
- }
766
- if (event.event === "alert resolved") {
767
- if (hasFullIntegration && event.resolution_time_mins) {
777
+ if (event.event === "alert resolved" && event.resolution_time_mins) {
768
778
  event.resolution_time_mins = Math.floor(event.resolution_time_mins * 0.5);
769
- event.integrated_team = true;
770
- } else {
771
- event.integrated_team = false;
772
779
  }
773
- }
774
- });
775
- }
776
-
777
- // ─────────────────────────────────────────────────────────────
778
- // Hook #5: DOCS READERS DEPLOY MORE (everything)
779
- // Users who read best_practices 3+ times get extra production deploys
780
- // ─────────────────────────────────────────────────────────────
781
- if (type === "everything") {
782
- const userEvents = record;
783
-
784
- // First pass: count best_practices documentation views
785
- let bestPracticesCount = 0;
786
- userEvents.forEach((event) => {
787
- if (event.event === "documentation viewed" && event.doc_section === "best_practices") {
788
- bestPracticesCount++;
789
- }
790
- });
780
+ });
781
+ }
791
782
 
792
- // Second pass: if 3+ best practices views, add extra production deploys
793
- if (bestPracticesCount >= 3) {
794
- 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) {
795
790
  const lastEvent = userEvents[userEvents.length - 1];
796
- const deployTemplate = userEvents.find(e => e.event === "service deployed");
797
- if (lastEvent && deployTemplate) {
798
- for (let i = 0; i < extraDeploys; i++) {
799
- const deployEvent = {
800
- ...deployTemplate,
801
- time: dayjs(lastEvent.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
802
- user_id: lastEvent.user_id,
803
- service_id: chance.pickone(serviceIds),
804
- service_type: chance.pickone(["web_app", "api", "database", "cache", "queue", "ml_model"]),
805
- environment: "production",
806
- cloud_provider: chance.pickone(["aws", "gcp", "azure"]),
807
- docs_informed: true,
808
- };
809
- 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);
810
807
  }
811
808
  }
812
809
  }
813
810
  }
814
811
 
815
- // ─────────────────────────────────────────────────────────────
816
- // Hook #8: ENTERPRISE VS STARTUP (user)
817
- // Company size determines seat count, ACV, and health score
818
- // ─────────────────────────────────────────────────────────────
812
+ // HOOK 8: ENTERPRISE VS STARTUP (user) — company size determines
813
+ // seat count, ACV, and CSM. Real profile attrs.
819
814
  if (type === "user") {
820
- // Hook #2 support: tag churned accounts on user profile for discoverability
821
- const idHash = String(record.distinct_id || "").split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
822
- record.churned_account = (idHash % 5) === 0;
823
-
824
815
  const companySize = record.company_size;
825
-
826
816
  if (companySize === "enterprise") {
827
817
  record.seat_count = chance.integer({ min: 50, max: 500 });
828
818
  record.annual_contract_value = chance.integer({ min: 50000, max: 500000 });
@@ -840,7 +830,6 @@ const config = {
840
830
  record.annual_contract_value = chance.integer({ min: 0, max: 3600 });
841
831
  record.customer_success_manager = false;
842
832
  }
843
-
844
833
  record.customer_health_score = chance.integer({ min: 1, max: 100 });
845
834
  }
846
835