@ak--47/dungeon-master 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +518 -0
  2. package/dungeons/array-of-object-lookup-schema.json +327 -0
  3. package/dungeons/array-of-object-lookup.js +220 -0
  4. package/dungeons/ecommerce-schema.json +462 -0
  5. package/dungeons/ecommerce.js +447 -0
  6. package/dungeons/education-schema.json +2409 -0
  7. package/dungeons/education.js +768 -0
  8. package/dungeons/fintech-schema.json +14034 -0
  9. package/dungeons/fintech.js +696 -0
  10. package/dungeons/foobar-schema.json +403 -0
  11. package/dungeons/foobar.js +296 -0
  12. package/dungeons/food-delivery-schema.json +192 -0
  13. package/dungeons/food-delivery.js +602 -0
  14. package/dungeons/food-schema.json +1152 -0
  15. package/dungeons/food.js +754 -0
  16. package/dungeons/gaming-schema.json +1270 -0
  17. package/dungeons/gaming.js +508 -0
  18. package/dungeons/insurance-application-schema.json +204 -0
  19. package/dungeons/insurance-application.js +605 -0
  20. package/dungeons/media-schema.json +906 -0
  21. package/dungeons/media.js +790 -0
  22. package/dungeons/retention-cadence-schema.json +78 -0
  23. package/dungeons/retention-cadence.js +244 -0
  24. package/dungeons/rpg-schema.json +4526 -0
  25. package/dungeons/rpg.js +919 -0
  26. package/dungeons/sanity-schema.json +255 -0
  27. package/dungeons/sanity.js +152 -0
  28. package/dungeons/sass-schema.json +1291 -0
  29. package/dungeons/sass.js +795 -0
  30. package/dungeons/scd-schema.json +919 -0
  31. package/dungeons/scd.js +277 -0
  32. package/dungeons/simple-schema.json +608 -0
  33. package/dungeons/simple.js +285 -0
  34. package/dungeons/simplest-schema.json +1418 -0
  35. package/dungeons/simplest.js +392 -0
  36. package/dungeons/social-schema.json +1118 -0
  37. package/dungeons/social.js +686 -0
  38. package/dungeons/text-generation-schema.json +3096 -0
  39. package/dungeons/text-generation.js +812 -0
  40. package/index.js +567 -0
  41. package/lib/core/config-validator.js +395 -0
  42. package/lib/core/context.js +204 -0
  43. package/lib/core/dungeon-loader.js +337 -0
  44. package/lib/core/storage.js +379 -0
  45. package/lib/generators/adspend.js +132 -0
  46. package/lib/generators/events.js +271 -0
  47. package/lib/generators/funnels.js +407 -0
  48. package/lib/generators/mirror.js +167 -0
  49. package/lib/generators/product-lookup.js +262 -0
  50. package/lib/generators/product-names.js +195 -0
  51. package/lib/generators/profiles.js +93 -0
  52. package/lib/generators/scd.js +124 -0
  53. package/lib/generators/text.js +1192 -0
  54. package/lib/orchestrators/mixpanel-sender.js +266 -0
  55. package/lib/orchestrators/user-loop.js +335 -0
  56. package/lib/templates/abbreviated.d.ts +169 -0
  57. package/lib/templates/defaults.js +1405 -0
  58. package/lib/templates/phrases.js +2526 -0
  59. package/lib/templates/schema.d.ts +173 -0
  60. package/lib/templates/soup-presets.js +188 -0
  61. package/lib/utils/function-registry.js +302 -0
  62. package/lib/utils/json-evaluator.js +172 -0
  63. package/lib/utils/logger.js +34 -0
  64. package/lib/utils/utils.js +1490 -0
  65. package/package.json +89 -0
  66. package/types.d.ts +865 -0
@@ -0,0 +1,795 @@
1
+ import dayjs from "dayjs";
2
+ import utc from "dayjs/plugin/utc.js";
3
+ import "dotenv/config";
4
+ import * as u from "../lib/utils/utils.js";
5
+ import * as v from "ak-tools";
6
+
7
+ const SEED = "harness-sass";
8
+ dayjs.extend(utc);
9
+ const chance = u.initChance(SEED);
10
+ const num_users = 5_000;
11
+ const days = 100;
12
+
13
+ /** @typedef {import("../types.js").Dungeon} Config */
14
+
15
+ /*
16
+ * ═══════════════════════════════════════════════════════════════════════════════
17
+ * DATASET OVERVIEW
18
+ * ═══════════════════════════════════════════════════════════════════════════════
19
+ *
20
+ * CLOUDFORGE - B2B Cloud Infrastructure Monitoring & Deployment Platform
21
+ *
22
+ * CloudForge is a B2B SaaS platform that combines infrastructure monitoring (like Datadog)
23
+ * with deployment automation (like Terraform). It serves engineering teams across companies
24
+ * of all sizes - from startups deploying their first microservice to enterprises managing
25
+ * thousands of services across multi-cloud environments.
26
+ *
27
+ * - 5,000 users over 100 days
28
+ * - 600K events across 18 event types (+ 1 hook-created event type)
29
+ * - 8 funnels (onboarding, monitoring, incident response, deployment, infra, team, docs, billing)
30
+ * - Group analytics (companies)
31
+ * - Desktop/browser only (B2B SaaS - no mobile devices)
32
+ *
33
+ * CORE PLATFORM:
34
+ * Teams create workspaces, deploy services across AWS/GCP/Azure, and monitor everything
35
+ * from a unified dashboard. The platform tracks uptime, latency, error rates, CPU/memory
36
+ * usage, and costs. When things go wrong, CloudForge triggers alerts that route through
37
+ * PagerDuty/Slack integrations, and on-call engineers acknowledge and resolve incidents
38
+ * using automated runbooks.
39
+ *
40
+ * PRICING MODEL:
41
+ * Four tiers: Free, Team, Business, Enterprise. Enterprise customers get dedicated
42
+ * customer success managers and annual contracts. Pricing based on seat count and
43
+ * resource usage.
44
+ */
45
+
46
+ /*
47
+ * ═══════════════════════════════════════════════════════════════════════════════
48
+ * ANALYTICS HOOKS
49
+ * ═══════════════════════════════════════════════════════════════════════════════
50
+ *
51
+ * 8 deliberately architected patterns hidden in the data, simulating real-world
52
+ * B2B SaaS behavior. Several hooks use event removal (splice), event replacement,
53
+ * and module-level closure state tracking via Map objects.
54
+ *
55
+ * ─────────────────────────────────────────────────────────────────────────────
56
+ * 1. END-OF-QUARTER SPIKE (event hook)
57
+ * ─────────────────────────────────────────────────────────────────────────────
58
+ *
59
+ * Days 80-90: billing events shift toward plan upgrades 40% of the time, and team
60
+ * member invitations are duplicated 50% of the time. Tagged: quarter_end_push: true.
61
+ *
62
+ * Mixpanel Report — Plan Upgrades Over Time:
63
+ * • Insights line chart
64
+ * • Event: "billing event", filter "event_type" = "plan_upgraded"
65
+ * • Daily trend
66
+ * • Expected: Spike in plan upgrades during days 80-90 (4x normal volume)
67
+ *
68
+ * Mixpanel Report — Team Expansion Surge:
69
+ * • Insights line chart
70
+ * • Event: "team member invited", filter "quarter_end_push" = true
71
+ * • Daily trend
72
+ * • Expected: Clear volume spike in last 10 days with duplicate invites
73
+ *
74
+ * ─────────────────────────────────────────────────────────────────────────────
75
+ * 2. CHURNED ACCOUNT SILENCING (everything hook)
76
+ * ─────────────────────────────────────────────────────────────────────────────
77
+ *
78
+ * ~10% of users (hash of distinct_id, idHash % 5 === 0) go completely silent
79
+ * after day 30. ALL events after month 1 are removed via splice(). User profiles
80
+ * are tagged churned_account: true for discoverability.
81
+ *
82
+ * Mixpanel Report — Churned Account Retention:
83
+ * • Retention report
84
+ * • Event A/B: Any event
85
+ * • Breakdown: User profile "churned_account"
86
+ * • Expected: churned_account=true shows 0% retention after day 30
87
+ *
88
+ * Mixpanel Report — Churned Account Activity:
89
+ * • Insights line chart
90
+ * • Event: Any event, measure total per user
91
+ * • Breakdown: User profile "churned_account"
92
+ * • Weekly trend
93
+ * • Expected: churned_account=true flatlines after week 4
94
+ *
95
+ * ─────────────────────────────────────────────────────────────────────────────
96
+ * 3. ALERT ESCALATION REPLACEMENT (event hook)
97
+ * ─────────────────────────────────────────────────────────────────────────────
98
+ *
99
+ * 30% of critical/emergency "alert triggered" events are REPLACED with a new
100
+ * event type "incident created" (not in the events array — hook-only). Includes
101
+ * escalation_level (P1/P2), teams_paged, incident_id.
102
+ *
103
+ * Mixpanel Report — Incident Created Discovery:
104
+ * • Insights report
105
+ * • Event: "incident created"
106
+ * • Breakdown: "escalation_level"
107
+ * • Expected: P1 and P2 incidents, ~30% of critical/emergency alert volume
108
+ *
109
+ * Mixpanel Report — Alert vs Incident Ratio:
110
+ * • Insights report
111
+ * • Events: "alert triggered" AND "incident created"
112
+ * • Expected: incident created count ~ 30% of critical+emergency alerts
113
+ *
114
+ * ─────────────────────────────────────────────────────────────────────────────
115
+ * 4. INTEGRATION USERS SUCCEED (everything hook)
116
+ * ─────────────────────────────────────────────────────────────────────────────
117
+ *
118
+ * Users with BOTH Slack AND PagerDuty integrations resolve alerts faster:
119
+ * response_time_mins reduced 60%, resolution_time_mins reduced 50%.
120
+ * Tagged: integrated_team: true.
121
+ *
122
+ * Mixpanel Report — Integration Impact on Response Time:
123
+ * • Insights report
124
+ * • Event: "alert acknowledged", measure avg "response_time_mins"
125
+ * • Breakdown: "integrated_team"
126
+ * • Expected: integrated_team=true ~ 60% lower response time
127
+ *
128
+ * Mixpanel Report — Integration Impact on Resolution:
129
+ * • Insights report
130
+ * • Event: "alert resolved", measure avg "resolution_time_mins"
131
+ * • Breakdown: "integrated_team"
132
+ * • Expected: integrated_team=true ~ 50% faster resolution
133
+ *
134
+ * ─────────────────────────────────────────────────────────────────────────────
135
+ * 5. DOCS READERS DEPLOY MORE (everything hook)
136
+ * ─────────────────────────────────────────────────────────────────────────────
137
+ *
138
+ * Users with 3+ "best_practices" documentation views get 2-3 extra production
139
+ * deploys spliced into their event stream. Tagged: docs_informed: true.
140
+ *
141
+ * Mixpanel Report — Docs-Informed Deployments:
142
+ * • Insights report
143
+ * • Event: "service deployed", filter "environment" = "production"
144
+ * • Breakdown: "docs_informed"
145
+ * • Expected: docs_informed=true shows extra production deployments
146
+ *
147
+ * Mixpanel Report — Docs Readers vs Non-Readers:
148
+ * • Insights report
149
+ * • Event: "service deployed", measure total per user
150
+ * • Segment: Users with 3+ "documentation viewed" (doc_section = "best_practices")
151
+ * • Expected: ~1.8x more production deploys per user for docs readers
152
+ *
153
+ * ─────────────────────────────────────────────────────────────────────────────
154
+ * 6. COST OVERRUN PATTERN (event hook — closure state)
155
+ * ─────────────────────────────────────────────────────────────────────────────
156
+ *
157
+ * When cost_change_percent > 25 on a "cost report generated" event, the user
158
+ * is stored in a module-level Map. Their next "infrastructure scaled" event
159
+ * is forced to scale_direction: "down". Tagged: budget_exceeded, cost_reaction.
160
+ *
161
+ * Mixpanel Report — Cost Overrun to Scale Down:
162
+ * • Insights report
163
+ * • Event: "infrastructure scaled"
164
+ * • Breakdown: "cost_reaction"
165
+ * • Expected: cost_reaction=true events are 100% scale_direction="down"
166
+ *
167
+ * ─────────────────────────────────────────────────────────────────────────────
168
+ * 7. FAILED DEPLOYMENT RECOVERY (event hook — closure state)
169
+ * ─────────────────────────────────────────────────────────────────────────────
170
+ *
171
+ * After a failed pipeline run, the user's next successful deploy has
172
+ * duration_sec * 1.5 (recovery deploys are slower). Tagged: recovery_deployment.
173
+ * Uses module-level Map for cross-call state.
174
+ *
175
+ * Mixpanel Report — Recovery Deploy Duration:
176
+ * • Insights report
177
+ * • Event: "deployment pipeline run", measure avg "duration_sec"
178
+ * • Breakdown: "recovery_deployment"
179
+ * • Expected: recovery_deployment=true ~ 1.5x longer duration
180
+ *
181
+ * ─────────────────────────────────────────────────────────────────────────────
182
+ * 8. ENTERPRISE VS STARTUP (user hook)
183
+ * ─────────────────────────────────────────────────────────────────────────────
184
+ *
185
+ * Company size determines seat_count, annual_contract_value, and
186
+ * customer_success_manager (enterprise only). All users get customer_health_score.
187
+ *
188
+ * Mixpanel Report — ACV by Company Size:
189
+ * • Insights report
190
+ * • Event: Any, measure unique users
191
+ * • Breakdown: User profile "company_size"
192
+ * • Expected: startup ($0-3.6K), smb ($3.6K-12K), mid_market ($12K-50K),
193
+ * enterprise ($50K-500K)
194
+ *
195
+ * ─────────────────────────────────────────────────────────────────────────────
196
+ * EXPECTED METRICS SUMMARY
197
+ * ─────────────────────────────────────────────────────────────────────────────
198
+ *
199
+ * Hook | Metric | Baseline | Hook Effect | Ratio
200
+ * -------------------------|--------------------------|-----------|----------------|------
201
+ * End-of-Quarter Spike | Plan upgrades/day | ~2/day | ~8/day | 4x
202
+ * Churned Accounts | Users active month 2 | 100% | 90% | 0.9x
203
+ * Alert Escalation | Incidents from alerts | 0% | ~30% of crit | new
204
+ * Integration Users | MTTR (minutes) | ~300 | ~150 | 0.5x
205
+ * Docs Readers | Prod deploys/user | ~3 | ~5-6 | 1.8x
206
+ * Cost Overrun | Scale-down after overrun | 50% | 100% | 2x
207
+ * Failed Deploy Recovery | Deploy duration (sec) | ~500 | ~750 | 1.5x
208
+ * Enterprise vs Startup | ACV range | $0-3.6K | $50K-500K | 100x+
209
+ *
210
+ * ─────────────────────────────────────────────────────────────────────────────
211
+ * ADVANCED ANALYSIS IDEAS
212
+ * ─────────────────────────────────────────────────────────────────────────────
213
+ *
214
+ * CROSS-HOOK PATTERNS:
215
+ * - Churned + Enterprise: Do churned accounts skew toward startups or are
216
+ * enterprise accounts also silenced?
217
+ * - Integration + Cost: Do teams with full integrations manage costs better?
218
+ * - Docs + Deploys + Failures: Do docs readers have fewer failed deployments?
219
+ * - Quarter Spike + Churn: Are quarter-end upgrades correlated with later churn?
220
+ * - Enterprise Recovery: Do enterprise customers recover from failed deploys
221
+ * differently than startups?
222
+ *
223
+ * COHORT ANALYSIS:
224
+ * - By company_size: Compare all metrics across startup/smb/mid_market/enterprise
225
+ * - By plan_tier: Free vs. Team vs. Business vs. Enterprise engagement
226
+ * - By cloud_provider: AWS vs. GCP vs. Azure deployment and alert patterns
227
+ * - By primary_role: Engineer vs. SRE vs. DevOps vs. Manager behaviors
228
+ *
229
+ * KEY METRICS:
230
+ * - MTTR: alert triggered → alert resolved duration
231
+ * - Deployment Frequency: service deployed per user per week
232
+ * - Deployment Success Rate: pipeline success vs. failure ratio
233
+ * - Cost Efficiency: total_cost trend over time per company
234
+ * - Feature Adoption: integration configured events by type
235
+ * - Documentation Engagement: documentation viewed by section
236
+ */
237
+
238
+ // Generate consistent IDs for lookup tables and event properties
239
+ const serviceIds = v.range(1, 201).map(() => `svc_${v.uid(8)}`);
240
+ const alertIds = v.range(1, 501).map(() => `alert_${v.uid(6)}`);
241
+ const pipelineIds = v.range(1, 101).map(() => `pipe_${v.uid(6)}`);
242
+ const runbookIds = v.range(1, 51).map(() => `rb_${v.uid(6)}`);
243
+ const companyIds = v.range(1, 301).map(() => `comp_${v.uid(8)}`);
244
+
245
+ // Module-level Maps for closure-based state tracking across hook calls
246
+ const costOverrunUsers = new Map();
247
+ const failedDeployUsers = new Map();
248
+
249
+ /** @type {Config} */
250
+ const config = {
251
+ token: "",
252
+ seed: SEED,
253
+ numDays: days,
254
+ numEvents: num_users * 120,
255
+ numUsers: num_users,
256
+ hasAnonIds: false,
257
+ hasSessionIds: true,
258
+ format: "json",
259
+ gzip: true,
260
+ alsoInferFunnels: false,
261
+ hasLocation: true,
262
+ hasAndroidDevices: false,
263
+ hasIOSDevices: false,
264
+ hasDesktopDevices: true,
265
+ hasBrowser: true,
266
+ hasCampaigns: false,
267
+ isAnonymous: false,
268
+ hasAdSpend: false,
269
+ percentUsersBornInDataset: 50,
270
+ hasAvatar: true,
271
+ batchSize: 2_500_000,
272
+ concurrency: 1,
273
+ writeToDisk: false,
274
+ scdProps: {},
275
+
276
+ funnels: [
277
+ {
278
+ sequence: ["workspace created", "service deployed", "dashboard viewed"],
279
+ isFirstFunnel: true,
280
+ conversionRate: 70,
281
+ timeToConvert: 2,
282
+ },
283
+ {
284
+ // Daily monitoring: dashboards, queries, API calls (most common)
285
+ sequence: ["dashboard viewed", "query executed", "api call"],
286
+ conversionRate: 80,
287
+ timeToConvert: 0.5,
288
+ weight: 5,
289
+ },
290
+ {
291
+ // Incident response pipeline
292
+ sequence: ["alert triggered", "alert acknowledged", "alert resolved"],
293
+ conversionRate: 55,
294
+ timeToConvert: 6,
295
+ weight: 4,
296
+ },
297
+ {
298
+ // Deployment cycle
299
+ sequence: ["deployment pipeline run", "service deployed", "dashboard viewed"],
300
+ conversionRate: 65,
301
+ timeToConvert: 1,
302
+ weight: 3,
303
+ },
304
+ {
305
+ // Infrastructure management
306
+ sequence: ["cost report generated", "infrastructure scaled", "security scan"],
307
+ conversionRate: 50,
308
+ timeToConvert: 4,
309
+ weight: 2,
310
+ },
311
+ {
312
+ // Team and config management
313
+ sequence: ["team member invited", "integration configured", "feature flag toggled"],
314
+ conversionRate: 40,
315
+ timeToConvert: 8,
316
+ weight: 2,
317
+ },
318
+ {
319
+ // Documentation and runbook usage
320
+ sequence: ["documentation viewed", "runbook executed", "service deployed"],
321
+ conversionRate: 45,
322
+ timeToConvert: 2,
323
+ weight: 2,
324
+ },
325
+ {
326
+ // Billing and account management
327
+ sequence: ["billing event", "dashboard viewed"],
328
+ conversionRate: 60,
329
+ timeToConvert: 1,
330
+ weight: 1,
331
+ },
332
+ ],
333
+
334
+ events: [
335
+ {
336
+ event: "workspace created",
337
+ weight: 1,
338
+ isFirstEvent: true,
339
+ properties: {
340
+ company_size: ["startup", "smb", "mid_market", "enterprise"],
341
+ industry: ["tech", "finance", "healthcare", "retail", "media"],
342
+ }
343
+ },
344
+ {
345
+ event: "service deployed",
346
+ weight: 10,
347
+ properties: {
348
+ service_id: u.pickAWinner(serviceIds),
349
+ service_type: ["web_app", "api", "database", "cache", "queue", "ml_model"],
350
+ environment: ["production", "staging", "dev"],
351
+ cloud_provider: ["aws", "gcp", "azure"],
352
+ }
353
+ },
354
+ {
355
+ event: "dashboard viewed",
356
+ weight: 20,
357
+ properties: {
358
+ dashboard_type: ["overview", "cost", "performance", "security", "custom"],
359
+ time_range: ["1h", "6h", "24h", "7d", "30d"],
360
+ }
361
+ },
362
+ {
363
+ event: "alert triggered",
364
+ weight: 12,
365
+ properties: {
366
+ alert_id: u.pickAWinner(alertIds),
367
+ severity: ["info", "warning", "critical", "emergency"],
368
+ alert_type: ["cpu", "memory", "latency", "error_rate", "disk", "network"],
369
+ service_id: u.pickAWinner(serviceIds),
370
+ }
371
+ },
372
+ {
373
+ event: "alert acknowledged",
374
+ weight: 8,
375
+ properties: {
376
+ alert_id: u.pickAWinner(alertIds),
377
+ response_time_mins: u.weighNumRange(1, 120),
378
+ acknowledged_by_role: ["engineer", "sre", "manager", "oncall"],
379
+ }
380
+ },
381
+ {
382
+ event: "alert resolved",
383
+ weight: 7,
384
+ properties: {
385
+ alert_id: u.pickAWinner(alertIds),
386
+ resolution_time_mins: u.weighNumRange(5, 1440),
387
+ root_cause: ["config_change", "capacity", "bug", "dependency", "network"],
388
+ }
389
+ },
390
+ {
391
+ event: "deployment pipeline run",
392
+ weight: 9,
393
+ properties: {
394
+ pipeline_id: u.pickAWinner(pipelineIds),
395
+ status: ["success", "failed", "cancelled"],
396
+ duration_sec: u.weighNumRange(30, 1800),
397
+ commit_count: u.weighNumRange(1, 20),
398
+ }
399
+ },
400
+ {
401
+ event: "infrastructure scaled",
402
+ weight: 5,
403
+ properties: {
404
+ service_id: u.pickAWinner(serviceIds),
405
+ scale_direction: u.pickAWinner(["up", "down"], 0.15),
406
+ previous_capacity: u.weighNumRange(1, 100),
407
+ new_capacity: u.weighNumRange(1, 100),
408
+ auto_scaled: u.pickAWinner([true, false], 0.15),
409
+ }
410
+ },
411
+ {
412
+ event: "cost report generated",
413
+ weight: 4,
414
+ properties: {
415
+ report_period: ["daily", "weekly", "monthly"],
416
+ total_cost: u.weighNumRange(100, 50000),
417
+ cost_change_percent: u.weighNumRange(-30, 50),
418
+ }
419
+ },
420
+ {
421
+ event: "team member invited",
422
+ weight: 3,
423
+ properties: {
424
+ role: ["admin", "editor", "viewer", "billing"],
425
+ invitation_method: ["email", "sso", "slack"],
426
+ }
427
+ },
428
+ {
429
+ event: "integration configured",
430
+ weight: 4,
431
+ properties: {
432
+ integration_type: ["slack", "pagerduty", "jira", "github", "datadog", "terraform"],
433
+ status: ["active", "paused", "error"],
434
+ }
435
+ },
436
+ {
437
+ event: "query executed",
438
+ weight: 15,
439
+ properties: {
440
+ query_type: ["metrics", "logs", "traces"],
441
+ time_range_hours: u.weighNumRange(1, 720),
442
+ result_count: u.weighNumRange(0, 10000),
443
+ }
444
+ },
445
+ {
446
+ event: "runbook executed",
447
+ weight: 3,
448
+ properties: {
449
+ runbook_id: u.pickAWinner(runbookIds),
450
+ trigger: ["manual", "automated", "alert_triggered"],
451
+ success: u.pickAWinner([true, false], 0.15),
452
+ }
453
+ },
454
+ {
455
+ event: "billing event",
456
+ weight: 3,
457
+ properties: {
458
+ event_type: ["invoice_generated", "payment_received", "payment_failed", "plan_upgraded", "plan_downgraded"],
459
+ amount: u.weighNumRange(99, 25000),
460
+ }
461
+ },
462
+ {
463
+ event: "security scan",
464
+ weight: 6,
465
+ properties: {
466
+ scan_type: ["vulnerability", "compliance", "access_audit"],
467
+ findings_count: u.weighNumRange(0, 50),
468
+ critical_findings: u.weighNumRange(0, 10),
469
+ }
470
+ },
471
+ {
472
+ event: "api call",
473
+ weight: 16,
474
+ properties: {
475
+ endpoint: ["/deploy", "/status", "/metrics", "/alerts", "/config", "/billing"],
476
+ method: ["GET", "POST", "PUT", "DELETE"],
477
+ response_time_ms: u.weighNumRange(10, 5000),
478
+ status_code: u.pickAWinner([200, 201, 400, 401, 403, 500, 503]),
479
+ }
480
+ },
481
+ {
482
+ event: "documentation viewed",
483
+ weight: 7,
484
+ properties: {
485
+ doc_section: ["getting_started", "api_reference", "best_practices", "troubleshooting", "changelog"],
486
+ time_on_page_sec: u.weighNumRange(5, 600),
487
+ }
488
+ },
489
+ {
490
+ event: "feature flag toggled",
491
+ weight: 4,
492
+ properties: {
493
+ flag_name: () => `flag_${chance.word()}`,
494
+ new_state: u.pickAWinner(["enabled", "disabled"], 0.15),
495
+ environment: ["production", "staging", "dev"],
496
+ }
497
+ },
498
+ ],
499
+
500
+ superProps: {
501
+ plan_tier: u.pickAWinner(["free", "free", "team", "team", "business", "enterprise"]),
502
+ cloud_provider: ["aws", "gcp", "azure", "multi_cloud"],
503
+ },
504
+
505
+ userProps: {
506
+ company_size: u.pickAWinner(["startup", "startup", "smb", "mid_market", "enterprise"]),
507
+ primary_role: ["engineer", "sre", "devops", "manager", "executive"],
508
+ team_name: ["Platform", "Backend", "Frontend", "Data", "Security", "Infrastructure"],
509
+ },
510
+
511
+ groupKeys: [
512
+ ["company_id", 300, ["workspace created", "service deployed", "billing event", "team member invited"]],
513
+ ],
514
+
515
+ groupProps: {
516
+ company_id: {
517
+ name: () => `${chance.word({ capitalize: true })} ${chance.pickone(["Systems", "Technologies", "Labs", "Cloud", "Digital", "Networks", "Solutions"])}`,
518
+ industry: ["tech", "finance", "healthcare", "retail", "media", "manufacturing", "logistics"],
519
+ employee_count: ["1-10", "11-50", "51-200", "201-1000", "1001-5000", "5000+"],
520
+ arr_bucket: ["<10k", "10k-50k", "50k-200k", "200k-1M", "1M+"],
521
+ }
522
+ },
523
+
524
+ lookupTables: [],
525
+
526
+ /**
527
+ * ARCHITECTED ANALYTICS HOOKS
528
+ *
529
+ * This hook function creates 8 deliberate patterns in the data:
530
+ *
531
+ * 1. END-OF-QUARTER SPIKE: Days 80-90 drive plan upgrades and team expansion
532
+ * 2. CHURNED ACCOUNT SILENCING: ~10% of users go completely silent after month 1
533
+ * 3. ALERT ESCALATION REPLACEMENT: Critical alerts become "incident created" events
534
+ * 4. INTEGRATION USERS SUCCEED: Slack+PagerDuty users resolve incidents 50-60% faster
535
+ * 5. DOCS READERS DEPLOY MORE: Best practices readers get extra production deploys
536
+ * 6. COST OVERRUN PATTERN: Budget-exceeded users react by scaling down infrastructure
537
+ * 7. FAILED DEPLOYMENT RECOVERY: Recovery deploys take 1.5x longer, tracked across calls
538
+ * 8. ENTERPRISE VS STARTUP: Company size determines seat count, ACV, and health score
539
+ */
540
+ hook: function (record, type, meta) {
541
+ const NOW = dayjs();
542
+ const DATASET_START = NOW.subtract(days, "days");
543
+
544
+ // ─────────────────────────────────────────────────────────────
545
+ // Hook #1: END-OF-QUARTER SPIKE (event)
546
+ // Days 80-90: billing upgrades and team expansion surge
547
+ // ─────────────────────────────────────────────────────────────
548
+ if (type === "event") {
549
+ const EVENT_TIME = dayjs(record.time);
550
+ const dayInDataset = EVENT_TIME.diff(DATASET_START, "days", true);
551
+
552
+ if (record.event === "billing event") {
553
+ if (dayInDataset >= 80 && dayInDataset <= 90 && chance.bool({ likelihood: 40 })) {
554
+ record.event_type = "plan_upgraded";
555
+ record.quarter_end_push = true;
556
+ } else {
557
+ record.quarter_end_push = false;
558
+ }
559
+ }
560
+
561
+ if (record.event === "team member invited") {
562
+ if (dayInDataset >= 80 && dayInDataset <= 90) {
563
+ record.quarter_end_push = true;
564
+ // 50% of the time duplicate the invite event (hiring push)
565
+ if (chance.bool({ likelihood: 50 })) {
566
+ return {
567
+ event: "team member invited",
568
+ time: EVENT_TIME.add(chance.integer({ min: 1, max: 60 }), "minutes").toISOString(),
569
+ user_id: record.user_id,
570
+ role: chance.pickone(["editor", "viewer"]),
571
+ invitation_method: chance.pickone(["email", "sso", "slack"]),
572
+ quarter_end_push: true,
573
+ duplicate_invite: true,
574
+ };
575
+ }
576
+ } else {
577
+ record.quarter_end_push = false;
578
+ }
579
+ }
580
+ }
581
+
582
+ // ─────────────────────────────────────────────────────────────
583
+ // Hook #3: ALERT ESCALATION REPLACEMENT (event)
584
+ // Critical/emergency alerts sometimes become formal incidents
585
+ // ─────────────────────────────────────────────────────────────
586
+ if (type === "event") {
587
+ if (record.event === "alert triggered") {
588
+ const severity = record.severity;
589
+ if ((severity === "critical" || severity === "emergency") && chance.bool({ likelihood: 30 })) {
590
+ // REPLACE the event entirely with an "incident created" event
591
+ return {
592
+ event: "incident created",
593
+ time: record.time,
594
+ user_id: record.user_id,
595
+ escalation_level: chance.pickone(["P1", "P2"]),
596
+ teams_paged: chance.integer({ min: 1, max: 5 }),
597
+ incident_id: `inc_${v.uid(8)}`,
598
+ original_severity: severity,
599
+ original_alert_type: record.alert_type,
600
+ service_id: record.service_id,
601
+ auto_escalated: true,
602
+ };
603
+ }
604
+ }
605
+ }
606
+
607
+ // ─────────────────────────────────────────────────────────────
608
+ // Hook #6: COST OVERRUN PATTERN (event)
609
+ // Budget-exceeded users react by scaling down infrastructure
610
+ // Uses module-level costOverrunUsers Map for cross-call state
611
+ // ─────────────────────────────────────────────────────────────
612
+ if (type === "event") {
613
+ if (record.event === "cost report generated") {
614
+ const costChange = record.cost_change_percent;
615
+ if (costChange > 25) {
616
+ record.cost_alert = true;
617
+ record.budget_exceeded = true;
618
+ costOverrunUsers.set(record.user_id, true);
619
+ } else {
620
+ record.cost_alert = false;
621
+ record.budget_exceeded = false;
622
+ }
623
+ }
624
+
625
+ if (record.event === "infrastructure scaled") {
626
+ if (costOverrunUsers.has(record.user_id)) {
627
+ record.scale_direction = "down";
628
+ record.cost_reaction = true;
629
+ costOverrunUsers.delete(record.user_id);
630
+ } else {
631
+ record.cost_reaction = false;
632
+ }
633
+ }
634
+ }
635
+
636
+ // ─────────────────────────────────────────────────────────────
637
+ // Hook #7: FAILED DEPLOYMENT RECOVERY (event)
638
+ // Recovery deploys take 1.5x longer after a failure
639
+ // Uses module-level failedDeployUsers Map for cross-call state
640
+ // ─────────────────────────────────────────────────────────────
641
+ if (type === "event") {
642
+ if (record.event === "deployment pipeline run") {
643
+ const status = record.status;
644
+ if (status === "failed") {
645
+ failedDeployUsers.set(record.user_id, true);
646
+ record.recovery_deployment = false;
647
+ } else if (status === "success" && failedDeployUsers.has(record.user_id)) {
648
+ record.duration_sec = Math.floor((record.duration_sec || 300) * 1.5);
649
+ record.recovery_deployment = true;
650
+ failedDeployUsers.delete(record.user_id);
651
+ } else {
652
+ record.recovery_deployment = false;
653
+ }
654
+ }
655
+ }
656
+
657
+ // ─────────────────────────────────────────────────────────────
658
+ // Hook #2: CHURNED ACCOUNT SILENCING (everything)
659
+ // ~20% targeted (hash % 5), yielding ~10% visible after accounting for invisible churned users
660
+ // ─────────────────────────────────────────────────────────────
661
+ if (type === "everything") {
662
+ const userEvents = record;
663
+ if (userEvents && userEvents.length > 0) {
664
+ const firstEvent = userEvents[0];
665
+ const idHash = String(firstEvent.user_id || firstEvent.device_id).split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
666
+ const isChurnedAccount = (idHash % 5) === 0;
667
+
668
+ if (isChurnedAccount) {
669
+ for (let i = userEvents.length - 1; i >= 0; i--) {
670
+ const evt = userEvents[i];
671
+ const dayInDataset = dayjs(evt.time).diff(DATASET_START, "days", true);
672
+ if (dayInDataset > 30) {
673
+ userEvents.splice(i, 1);
674
+ }
675
+ }
676
+ }
677
+ }
678
+ }
679
+
680
+ // ─────────────────────────────────────────────────────────────
681
+ // Hook #4: INTEGRATION USERS SUCCEED (everything)
682
+ // Users with both Slack AND PagerDuty integrations resolve faster
683
+ // ─────────────────────────────────────────────────────────────
684
+ if (type === "everything") {
685
+ const userEvents = record;
686
+
687
+ // First pass: check if user has both slack and pagerduty integrations
688
+ let hasSlack = false;
689
+ let hasPagerduty = false;
690
+
691
+ userEvents.forEach((event) => {
692
+ if (event.event === "integration configured") {
693
+ const integrationType = event.integration_type;
694
+ if (integrationType === "slack") hasSlack = true;
695
+ if (integrationType === "pagerduty") hasPagerduty = true;
696
+ }
697
+ });
698
+
699
+ const hasFullIntegration = hasSlack && hasPagerduty;
700
+
701
+ // Second pass: set integrated_team on all alert events, then boost for integrated users
702
+ userEvents.forEach((event) => {
703
+ if (event.event === "alert acknowledged") {
704
+ if (hasFullIntegration && event.response_time_mins) {
705
+ event.response_time_mins = Math.floor(event.response_time_mins * 0.4);
706
+ event.integrated_team = true;
707
+ } else {
708
+ event.integrated_team = false;
709
+ }
710
+ }
711
+ if (event.event === "alert resolved") {
712
+ if (hasFullIntegration && event.resolution_time_mins) {
713
+ event.resolution_time_mins = Math.floor(event.resolution_time_mins * 0.5);
714
+ event.integrated_team = true;
715
+ } else {
716
+ event.integrated_team = false;
717
+ }
718
+ }
719
+ });
720
+ }
721
+
722
+ // ─────────────────────────────────────────────────────────────
723
+ // Hook #5: DOCS READERS DEPLOY MORE (everything)
724
+ // Users who read best_practices 3+ times get extra production deploys
725
+ // ─────────────────────────────────────────────────────────────
726
+ if (type === "everything") {
727
+ const userEvents = record;
728
+
729
+ // First pass: count best_practices documentation views
730
+ let bestPracticesCount = 0;
731
+ userEvents.forEach((event) => {
732
+ if (event.event === "documentation viewed" && event.doc_section === "best_practices") {
733
+ bestPracticesCount++;
734
+ }
735
+ });
736
+
737
+ // Second pass: if 3+ best practices views, add extra production deploys
738
+ if (bestPracticesCount >= 3) {
739
+ const extraDeploys = chance.integer({ min: 2, max: 3 });
740
+ const lastEvent = userEvents[userEvents.length - 1];
741
+ if (lastEvent) {
742
+ for (let i = 0; i < extraDeploys; i++) {
743
+ const deployEvent = {
744
+ event: "service deployed",
745
+ time: dayjs(lastEvent.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
746
+ user_id: lastEvent.user_id,
747
+ service_id: chance.pickone(serviceIds),
748
+ service_type: chance.pickone(["web_app", "api", "database", "cache", "queue", "ml_model"]),
749
+ environment: "production",
750
+ cloud_provider: chance.pickone(["aws", "gcp", "azure"]),
751
+ docs_informed: true,
752
+ };
753
+ userEvents.splice(userEvents.length, 0, deployEvent);
754
+ }
755
+ }
756
+ }
757
+ }
758
+
759
+ // ─────────────────────────────────────────────────────────────
760
+ // Hook #8: ENTERPRISE VS STARTUP (user)
761
+ // Company size determines seat count, ACV, and health score
762
+ // ─────────────────────────────────────────────────────────────
763
+ if (type === "user") {
764
+ // Hook #2 support: tag churned accounts on user profile for discoverability
765
+ const idHash = String(record.distinct_id || "").split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
766
+ record.churned_account = (idHash % 5) === 0;
767
+
768
+ const companySize = record.company_size;
769
+
770
+ if (companySize === "enterprise") {
771
+ record.seat_count = chance.integer({ min: 50, max: 500 });
772
+ record.annual_contract_value = chance.integer({ min: 50000, max: 500000 });
773
+ record.customer_success_manager = true;
774
+ } else if (companySize === "mid_market") {
775
+ record.seat_count = chance.integer({ min: 10, max: 50 });
776
+ record.annual_contract_value = chance.integer({ min: 12000, max: 50000 });
777
+ record.customer_success_manager = false;
778
+ } else if (companySize === "smb") {
779
+ record.seat_count = chance.integer({ min: 3, max: 10 });
780
+ record.annual_contract_value = chance.integer({ min: 3600, max: 12000 });
781
+ record.customer_success_manager = false;
782
+ } else if (companySize === "startup") {
783
+ record.seat_count = chance.integer({ min: 1, max: 5 });
784
+ record.annual_contract_value = chance.integer({ min: 0, max: 3600 });
785
+ record.customer_success_manager = false;
786
+ }
787
+
788
+ record.customer_health_score = chance.integer({ min: 1, max: 100 });
789
+ }
790
+
791
+ return record;
792
+ }
793
+ };
794
+
795
+ export default config;