@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.
- package/dungeons/technical/array-of-object-lookup.js +0 -2
- package/dungeons/technical/simple.js +3 -4
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/text-generation.js +1 -1
- package/dungeons/vertical/ai-platform.js +192 -133
- package/dungeons/vertical/community.js +64 -34
- package/dungeons/vertical/crypto.js +333 -224
- package/dungeons/vertical/dating.js +228 -282
- package/dungeons/vertical/devtools.js +164 -62
- package/dungeons/vertical/ecommerce.js +240 -91
- package/dungeons/vertical/education.js +328 -310
- package/dungeons/vertical/fintech.js +437 -311
- package/dungeons/vertical/fitness.js +141 -58
- package/dungeons/vertical/food-delivery.js +298 -318
- package/dungeons/vertical/gaming.js +378 -231
- package/dungeons/vertical/healthcare.js +140 -60
- package/dungeons/vertical/insurance-application.js +168 -73
- package/dungeons/vertical/logistics.js +95 -6
- package/dungeons/vertical/marketplace.js +137 -51
- package/dungeons/vertical/media.js +241 -386
- package/dungeons/vertical/real-estate.js +322 -315
- package/dungeons/vertical/sass.js +253 -263
- package/dungeons/vertical/social.js +262 -206
- package/dungeons/vertical/travel.js +59 -16
- package/index.js +17 -17
- package/lib/core/config-validator.js +93 -21
- 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 +47 -47
- package/lib/utils/utils.js +71 -39
- package/package.json +1 -1
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +38 -15
- 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/vertical/ai-platform-schema.json +0 -617
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/crypto-schema.json +0 -546
- package/dungeons/vertical/dating-schema.json +0 -401
- 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 -2703
- 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/real-estate-schema.json +0 -527
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- 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
|
-
*
|
|
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,7 +321,9 @@ const failedDeployUsers = new Map();
|
|
|
257
321
|
const config = {
|
|
258
322
|
token,
|
|
259
323
|
seed: SEED,
|
|
260
|
-
|
|
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
|
-
|
|
591
|
-
|
|
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(
|
|
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
|
-
|
|
612
|
-
record
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
655
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
672
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
690
|
-
if (status === "failed") {
|
|
706
|
+
if (record.status === "failed") {
|
|
691
707
|
failedDeployUsers.set(record.user_id, true);
|
|
692
|
-
|
|
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
|
-
//
|
|
705
|
-
//
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
if (isChurnedAccount) {
|
|
751
|
+
if ((idHash % 5) === 0) {
|
|
723
752
|
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
724
|
-
const
|
|
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
|
-
|
|
736
|
-
|
|
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
|
-
|
|
748
|
-
if (
|
|
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
|
-
|
|
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
|
-
//
|
|
792
|
-
|
|
793
|
-
|
|
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
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
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
|
-
//
|
|
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
|
|