@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.
- package/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -4
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +5 -6
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +3 -3
- package/dungeons/vertical/ai-platform.js +858 -0
- package/dungeons/vertical/community.js +84 -40
- package/dungeons/vertical/crypto.js +830 -0
- package/dungeons/vertical/dating.js +744 -0
- package/dungeons/vertical/devtools.js +175 -69
- package/dungeons/vertical/ecommerce.js +242 -94
- package/dungeons/vertical/education.js +330 -313
- package/dungeons/vertical/fintech.js +442 -313
- package/dungeons/vertical/fitness.js +143 -61
- package/dungeons/vertical/food-delivery.js +327 -353
- package/dungeons/vertical/gaming.js +912 -382
- package/dungeons/vertical/healthcare.js +142 -63
- package/dungeons/vertical/insurance-application.js +170 -76
- package/dungeons/vertical/logistics.js +115 -20
- package/dungeons/vertical/marketplace.js +152 -58
- package/dungeons/vertical/media.js +248 -384
- package/dungeons/vertical/real-estate.js +781 -0
- package/dungeons/vertical/sass.js +255 -266
- package/dungeons/vertical/social.js +264 -206
- package/dungeons/vertical/travel.js +117 -41
- package/index.js +17 -17
- package/lib/core/config-validator.js +159 -31
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +11 -14
- package/lib/generators/funnels.js +12 -4
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/orchestrators/user-loop.js +81 -47
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/lib/utils/utils.js +71 -39
- package/package.json +8 -2
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +251 -51
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -438
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/rpg-schema.json +0 -2491
- package/dungeons/vertical/rpg.js +0 -976
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- 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
|
|
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
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
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
|
|
67
|
+
* 1. END-OF-QUARTER SPIKE (event)
|
|
64
68
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
65
69
|
*
|
|
66
|
-
* Days 80-90
|
|
67
|
-
* member invitations are duplicated 50% of the time.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
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
|
|
95
|
+
* 2. CHURNED ACCOUNT SILENCING (everything)
|
|
83
96
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
84
97
|
*
|
|
85
|
-
* ~10% of users (
|
|
86
|
-
* after day 30.
|
|
87
|
-
*
|
|
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
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
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
|
-
*
|
|
96
|
-
*
|
|
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
|
|
121
|
+
* 3. ALERT ESCALATION REPLACEMENT (event)
|
|
104
122
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
105
123
|
*
|
|
106
|
-
* 30% of critical/emergency "alert triggered" events are REPLACED
|
|
107
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
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
|
|
147
|
+
* 4. INTEGRATION USERS SUCCEED (everything)
|
|
123
148
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
124
149
|
*
|
|
125
|
-
* Users with BOTH
|
|
126
|
-
* response_time_mins reduced 60%, resolution_time_mins
|
|
127
|
-
*
|
|
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
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
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
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
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
|
|
173
|
+
* 5. DOCS READERS DEPLOY MORE (everything)
|
|
143
174
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
144
175
|
*
|
|
145
|
-
* Users with 3+ "
|
|
146
|
-
*
|
|
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
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
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
|
-
*
|
|
155
|
-
*
|
|
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
|
|
194
|
+
* 6. COST OVERRUN PATTERN (event — closure state)
|
|
162
195
|
* ─────────────────────────────────────────────────────────────────────────────
|
|
163
196
|
*
|
|
164
|
-
* When cost_change_percent > 25 on a "cost report generated" event,
|
|
165
|
-
* is stored in a module-level Map. Their next "infrastructure scaled"
|
|
166
|
-
* is forced to scale_direction
|
|
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
|
-
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
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
|
|
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).
|
|
180
|
-
*
|
|
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
|
-
*
|
|
183
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
592
|
-
|
|
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(
|
|
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
|
-
|
|
613
|
-
record
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
656
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
673
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
691
|
-
if (status === "failed") {
|
|
706
|
+
if (record.status === "failed") {
|
|
692
707
|
failedDeployUsers.set(record.user_id, true);
|
|
693
|
-
|
|
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
|
-
//
|
|
706
|
-
//
|
|
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
|
-
|
|
722
|
-
|
|
723
|
-
if (isChurnedAccount) {
|
|
751
|
+
if ((idHash % 5) === 0) {
|
|
724
752
|
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
725
|
-
const
|
|
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
|
-
|
|
737
|
-
|
|
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
|
-
|
|
749
|
-
if (
|
|
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
|
-
|
|
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
|
-
//
|
|
793
|
-
|
|
794
|
-
|
|
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
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
-
//
|
|
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
|
|