@ak--47/dungeon-master 1.5.3 → 1.6.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.
- package/.claude/skills/analyze-soup/SKILL.md +21 -12
- package/.claude/skills/create-dungeon/SKILL.md +65 -5
- package/.claude/skills/create-project/SKILL.md +96 -0
- package/.claude/skills/create-project/provision.mjs +336 -0
- package/.claude/skills/verify-dungeon/SKILL.md +56 -25
- package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
- package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
- package/.claude/skills/write-hooks/SKILL.md +156 -18
- package/CHANGELOG.md +211 -0
- package/HOOKS.md +566 -112
- package/README.md +28 -9
- package/docs/guides/1.3.0-upgrade-guide.md +262 -0
- package/docs/guides/1.3.1-upgrade-guide.md +328 -0
- package/docs/guides/1.4.0-upgrade-guide.md +130 -0
- package/docs/guides/1.4.1-upgrade-guide.md +125 -0
- package/docs/guides/1.4.3-upgrade-guide.md +200 -0
- package/docs/guides/1.4.4-upgrade-guide.md +95 -0
- package/docs/guides/1.5.0-upgrade-guide.md +426 -0
- package/docs/guides/1.5.1-upgrade-guide.md +178 -0
- package/docs/guides/1.5.3-upgrade-guide.md +117 -0
- package/docs/guides/1.6.0-upgrade-guide.md +204 -0
- package/dungeons/technical/stories-verify.js +152 -0
- package/dungeons/vertical/README.md +154 -0
- package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
- package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
- package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
- package/dungeons/vertical/community/community.js +1589 -0
- package/dungeons/vertical/community/community.sql +216 -0
- package/dungeons/vertical/community/community.verify.mjs +72 -0
- package/dungeons/vertical/crypto/crypto.js +1483 -0
- package/dungeons/vertical/crypto/crypto.sql +194 -0
- package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
- package/dungeons/vertical/dating/dating.js +1540 -0
- package/dungeons/vertical/dating/dating.sql +288 -0
- package/dungeons/vertical/dating/dating.verify.mjs +72 -0
- package/dungeons/vertical/devtools/devtools.js +1524 -0
- package/dungeons/vertical/devtools/devtools.sql +161 -0
- package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
- package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
- package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
- package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
- package/dungeons/vertical/education/education.js +1763 -0
- package/dungeons/vertical/education/education.sql +243 -0
- package/dungeons/vertical/education/education.verify.mjs +75 -0
- package/dungeons/vertical/fintech/fintech.js +1410 -0
- package/dungeons/vertical/fintech/fintech.sql +175 -0
- package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
- package/dungeons/vertical/fitness/fitness.js +1495 -0
- package/dungeons/vertical/fitness/fitness.sql +206 -0
- package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
- package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
- package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
- package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
- package/dungeons/vertical/gaming/gaming.js +1741 -0
- package/dungeons/vertical/gaming/gaming.sql +249 -0
- package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
- package/dungeons/vertical/healthcare/healthcare.js +1488 -0
- package/dungeons/vertical/healthcare/healthcare.sql +193 -0
- package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
- package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
- package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
- package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
- package/dungeons/vertical/logistics/logistics.js +1541 -0
- package/dungeons/vertical/logistics/logistics.sql +205 -0
- package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
- package/dungeons/vertical/marketplace/marketplace.js +1438 -0
- package/dungeons/vertical/marketplace/marketplace.sql +148 -0
- package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
- package/dungeons/vertical/{media.js → media/media.js} +557 -58
- package/dungeons/vertical/media/media.sql +173 -0
- package/dungeons/vertical/media/media.verify.mjs +74 -0
- package/dungeons/vertical/real-estate/real-estate.js +1629 -0
- package/dungeons/vertical/real-estate/real-estate.sql +167 -0
- package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
- package/dungeons/vertical/sass/sass.js +1733 -0
- package/dungeons/vertical/sass/sass.sql +173 -0
- package/dungeons/vertical/sass/sass.verify.mjs +74 -0
- package/dungeons/vertical/social/social.js +1456 -0
- package/dungeons/vertical/social/social.sql +160 -0
- package/dungeons/vertical/social/social.verify.mjs +76 -0
- package/dungeons/vertical/streaming/streaming.js +677 -0
- package/dungeons/vertical/streaming/streaming.sql +101 -0
- package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
- package/dungeons/vertical/support-desk/support-desk.js +741 -0
- package/dungeons/vertical/support-desk/support-desk.sql +137 -0
- package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
- package/dungeons/vertical/travel/travel.js +1603 -0
- package/dungeons/vertical/travel/travel.sql +236 -0
- package/dungeons/vertical/travel/travel.verify.mjs +72 -0
- package/index.js +2 -2
- package/lib/core/config-validator.js +37 -2
- package/lib/core/context.js +4 -2
- package/lib/core/dungeon-loader.js +20 -15
- package/lib/core/dungeon-to-json.js +22 -4
- package/lib/core/storage.js +3 -3
- package/lib/generators/funnels.js +36 -9
- package/lib/hook-helpers/cohort.js +53 -0
- package/lib/hook-helpers/index.js +8 -0
- package/lib/hook-helpers/mutate.js +1 -1
- package/lib/hook-helpers/shape.js +319 -0
- package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
- package/lib/hook-patterns/attributed-by-source.js +60 -58
- package/lib/hook-patterns/frequency-by-frequency.js +14 -4
- package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
- package/lib/hook-patterns/index.js +1 -1
- package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
- package/lib/orchestrators/user-loop.js +41 -0
- package/lib/templates/story-spec.schema.json +175 -0
- package/lib/utils/json-evaluator.js +1 -1
- package/lib/utils/utils.js +5 -4
- package/lib/verify/coerce.js +186 -0
- package/lib/verify/counting.js +270 -32
- package/lib/verify/emulate-breakdown.js +1285 -203
- package/lib/verify/first-time.js +89 -0
- package/lib/verify/flows.js +679 -0
- package/lib/verify/formula.js +259 -0
- package/lib/verify/funnel-engine.js +697 -113
- package/lib/verify/index.js +32 -4
- package/lib/verify/sessionize.js +275 -0
- package/lib/verify/story-runner.js +483 -0
- package/lib/verify/verify-dungeon.js +59 -37
- package/package.json +3 -2
- package/scripts/extract-dungeon-schema.mjs +2 -2
- package/scripts/run-many.mjs +2 -2
- package/scripts/verify-runner.mjs +2 -2
- package/scripts/verify-stories.mjs +244 -0
- package/types.d.ts +296 -30
- package/dungeons/vertical/ai-platform.js +0 -818
- package/dungeons/vertical/community.js +0 -966
- package/dungeons/vertical/crypto.js +0 -895
- package/dungeons/vertical/dating.js +0 -830
- package/dungeons/vertical/devtools.js +0 -1050
- package/dungeons/vertical/ecommerce.js +0 -811
- package/dungeons/vertical/education.js +0 -964
- package/dungeons/vertical/fintech.js +0 -946
- package/dungeons/vertical/fitness.js +0 -935
- package/dungeons/vertical/food-delivery.js +0 -934
- package/dungeons/vertical/gaming.js +0 -1120
- package/dungeons/vertical/healthcare.js +0 -945
- package/dungeons/vertical/insurance-application.js +0 -1039
- package/dungeons/vertical/logistics.js +0 -986
- package/dungeons/vertical/marketplace.js +0 -1019
- package/dungeons/vertical/real-estate.js +0 -872
- package/dungeons/vertical/sass.js +0 -995
- package/dungeons/vertical/social.js +0 -904
- package/dungeons/vertical/travel.js +0 -930
|
@@ -1,995 +0,0 @@
|
|
|
1
|
-
// ── IMPORTS ──
|
|
2
|
-
import dayjs from "dayjs";
|
|
3
|
-
import utc from "dayjs/plugin/utc.js";
|
|
4
|
-
dayjs.extend(utc);
|
|
5
|
-
import "dotenv/config";
|
|
6
|
-
import * as u from "../../lib/utils/utils.js";
|
|
7
|
-
import * as v from "ak-tools";
|
|
8
|
-
import { findFirstSequence, scaleFunnelTTC } from "../../lib/hook-helpers/timing.js";
|
|
9
|
-
/** @typedef {import("../../types").Dungeon} Config */
|
|
10
|
-
|
|
11
|
-
// ── OVERVIEW ──
|
|
12
|
-
/*
|
|
13
|
-
* NAME: CloudForge
|
|
14
|
-
* APP: B2B SaaS that fuses infrastructure monitoring (Datadog-style) with
|
|
15
|
-
* deployment automation (Terraform-style). Engineering teams create
|
|
16
|
-
* workspaces, deploy services across AWS/GCP/Azure, monitor uptime
|
|
17
|
-
* and cost, and respond to alerts via Slack/PagerDuty runbooks.
|
|
18
|
-
* Pricing: Free / Team / Business / Enterprise (seats + usage).
|
|
19
|
-
* SCALE: 10,000 users, ~1.4M events, 121 days (2026-01-01 → 2026-05-01)
|
|
20
|
-
* CORE LOOP: workspace created → service deployed → dashboard viewed → alert/resolve
|
|
21
|
-
*
|
|
22
|
-
* EVENTS (19):
|
|
23
|
-
* dashboard viewed (20) > api call (16) > query executed (15) > alert triggered (12)
|
|
24
|
-
* > service deployed (10) > deployment pipeline run (9) > alert acknowledged (8)
|
|
25
|
-
* > alert resolved (7) > documentation viewed (7) > security scan (6)
|
|
26
|
-
* > infrastructure scaled (5) > cost report generated (4) > integration configured (4)
|
|
27
|
-
* > feature flag toggled (4) > team member invited (3) > runbook executed (3)
|
|
28
|
-
* > billing event (3) > workspace created (1) > incident created (1)
|
|
29
|
-
*
|
|
30
|
-
* FUNNELS (8):
|
|
31
|
-
* - Onboarding: workspace created → service deployed → dashboard viewed (70%)
|
|
32
|
-
* - Daily Monitoring: dashboard viewed → query executed → api call (80%)
|
|
33
|
-
* - Incident Response: alert triggered → alert acknowledged → alert resolved (55%)
|
|
34
|
-
* - Deployment: deployment pipeline run → service deployed → dashboard viewed (65%, Canary A/B)
|
|
35
|
-
* - Infrastructure Mgmt: cost report generated → infrastructure scaled → security scan (50%)
|
|
36
|
-
* - Team & Config: team member invited → integration configured → feature flag toggled (40%)
|
|
37
|
-
* - Docs & Runbooks: documentation viewed → runbook executed → service deployed (45%)
|
|
38
|
-
* - Billing: billing event → dashboard viewed (60%)
|
|
39
|
-
*
|
|
40
|
-
* USER PROPS: company_size, primary_role, team_name, seat_count, annual_contract_value,
|
|
41
|
-
* customer_success_manager, customer_health_score, plan_tier, cloud_provider
|
|
42
|
-
* SUPER PROPS: plan_tier, cloud_provider
|
|
43
|
-
* SCD PROPS: primary_role (viewer/editor/admin/owner, monthly fuzzy, max 6),
|
|
44
|
-
* plan_tier (starter/growth/enterprise/scale, monthly fixed, max 6, company_id-scoped)
|
|
45
|
-
* GROUPS: company_id (300 companies)
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
// ── HOOK STORIES ──
|
|
49
|
-
/*
|
|
50
|
-
* NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable via
|
|
51
|
-
* behavioral cohorts or raw-prop breakdowns (company_size, day, doc_section).
|
|
52
|
-
*
|
|
53
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
54
|
-
* 1. END-OF-QUARTER SPIKE (everything)
|
|
55
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
-
*
|
|
57
|
-
* PATTERN: Days 100-110 billing events shift event_type toward "plan_upgraded"
|
|
58
|
-
* 40% of the time and team member invitations are duplicated 50% of the time.
|
|
59
|
-
* No flag — discover via line chart by day.
|
|
60
|
-
*
|
|
61
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
62
|
-
*
|
|
63
|
-
* Report 1: Plan Upgrades Over Time
|
|
64
|
-
* - Report type: Insights
|
|
65
|
-
* - Event: "billing event"
|
|
66
|
-
* - Measure: Total
|
|
67
|
-
* - Filter: event_type = "plan_upgraded"
|
|
68
|
-
* - Line chart by day
|
|
69
|
-
* - Expected: ~4x normal upgrade volume during days 100-110
|
|
70
|
-
*
|
|
71
|
-
* Report 2: Team Expansion Surge
|
|
72
|
-
* - Report type: Insights
|
|
73
|
-
* - Event: "team member invited"
|
|
74
|
-
* - Measure: Total
|
|
75
|
-
* - Line chart by day
|
|
76
|
-
* - Expected: clear volume spike in the final 10 days from duplicated invites
|
|
77
|
-
*
|
|
78
|
-
* REAL-WORLD ANALOGUE: B2B SaaS revenue clusters at quarter-close as sales
|
|
79
|
-
* teams pull deals forward and customers expand seats to lock in pricing.
|
|
80
|
-
*
|
|
81
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
-
* 2. CHURNED ACCOUNT SILENCING (everything)
|
|
83
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
84
|
-
*
|
|
85
|
-
* PATTERN: ~10-20% of users (deterministic via distinct_id char hash) go
|
|
86
|
-
* completely silent after day 30. All post-d30 events are removed via splice().
|
|
87
|
-
* No flag — derive cohort via behavioral retention bucket (users with zero
|
|
88
|
-
* activity past d30 vs the rest).
|
|
89
|
-
*
|
|
90
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
91
|
-
*
|
|
92
|
-
* Report 1: Retention Cliff
|
|
93
|
-
* - Cohort A: users with at least 1 event AFTER day 30
|
|
94
|
-
* - Cohort B: users with events ONLY in days 1-30
|
|
95
|
-
* - Compare cohort sizes — B should be ~10-20% of total
|
|
96
|
-
*
|
|
97
|
-
* Report 2: Activity Volume Pre/Post Day 30
|
|
98
|
-
* - Report type: Insights
|
|
99
|
-
* - Event: any event
|
|
100
|
-
* - Measure: Total per user
|
|
101
|
-
* - Line chart by day
|
|
102
|
-
* - Expected: a visible drop after d30 driven by the silent cohort
|
|
103
|
-
*
|
|
104
|
-
* REAL-WORLD ANALOGUE: Most SaaS churn happens silently — accounts simply
|
|
105
|
-
* stop logging in long before the formal cancellation lands.
|
|
106
|
-
*
|
|
107
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
108
|
-
* 3. ALERT ESCALATION REPLACEMENT (event)
|
|
109
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
110
|
-
*
|
|
111
|
-
* PATTERN: 30% of critical/emergency "alert triggered" events are REPLACED
|
|
112
|
-
* with a hook-only "incident created" event (not in the events array). The
|
|
113
|
-
* new event carries escalation_level (P1/P2), teams_paged, and incident_id.
|
|
114
|
-
*
|
|
115
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
116
|
-
*
|
|
117
|
-
* Report 1: Incident Created Discovery
|
|
118
|
-
* - Report type: Insights
|
|
119
|
-
* - Event: "incident created"
|
|
120
|
-
* - Measure: Total
|
|
121
|
-
* - Breakdown: "escalation_level"
|
|
122
|
-
* - Expected: P1 and P2 incidents, ~30% of critical/emergency alert volume
|
|
123
|
-
*
|
|
124
|
-
* Report 2: Alert vs Incident Ratio
|
|
125
|
-
* - Report type: Insights
|
|
126
|
-
* - Events: "alert triggered" AND "incident created"
|
|
127
|
-
* - Measure: Total
|
|
128
|
-
* - Expected: incident count ~ 30% of critical+emergency alert count
|
|
129
|
-
*
|
|
130
|
-
* REAL-WORLD ANALOGUE: Severe alerts get auto-promoted into incident
|
|
131
|
-
* tickets that page on-call engineers and trigger customer comms.
|
|
132
|
-
*
|
|
133
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
134
|
-
* 4. INTEGRATION USERS SUCCEED (everything)
|
|
135
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
136
|
-
*
|
|
137
|
-
* PATTERN: Users with BOTH "slack" AND "pagerduty" "integration configured"
|
|
138
|
-
* events resolve alerts faster: response_time_mins reduced 60%, resolution_time_mins
|
|
139
|
-
* reduced 50%. No flag — derive cohort behaviorally.
|
|
140
|
-
*
|
|
141
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
142
|
-
*
|
|
143
|
-
* Report 1: Response Time by Integration Cohort
|
|
144
|
-
* - Cohort A: users who configured BOTH slack AND pagerduty integrations
|
|
145
|
-
* - Cohort B: rest
|
|
146
|
-
* - Event: "alert acknowledged"
|
|
147
|
-
* - Measure: Average of "response_time_mins"
|
|
148
|
-
* - Expected: A ~ 60% lower response time
|
|
149
|
-
*
|
|
150
|
-
* Report 2: Resolution Time by Integration Cohort
|
|
151
|
-
* - Cohort A vs B (as above)
|
|
152
|
-
* - Event: "alert resolved"
|
|
153
|
-
* - Measure: Average of "resolution_time_mins"
|
|
154
|
-
* - Expected: A ~ 50% faster resolution
|
|
155
|
-
*
|
|
156
|
-
* REAL-WORLD ANALOGUE: Teams that wire alerting into their existing comms
|
|
157
|
-
* stack respond minutes faster — the alert literally finds the human.
|
|
158
|
-
*
|
|
159
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
160
|
-
* 5. DOCS READERS DEPLOY MORE (everything)
|
|
161
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
162
|
-
*
|
|
163
|
-
* PATTERN: Users with 3+ "documentation viewed" events where doc_section
|
|
164
|
-
* (or equivalent prop) indicates best-practices reading get 2-3 extra
|
|
165
|
-
* production deploys spliced in. No flag — derive cohort by counting
|
|
166
|
-
* doc views per user.
|
|
167
|
-
*
|
|
168
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
169
|
-
*
|
|
170
|
-
* Report 1: Per-User Deploy Volume by Docs Cohort
|
|
171
|
-
* - Cohort A: users with >= 3 "documentation viewed" events
|
|
172
|
-
* - Cohort B: users with < 3
|
|
173
|
-
* - Event: "service deployed"
|
|
174
|
-
* - Measure: Total per user
|
|
175
|
-
* - Expected: A ~ 1.8x B
|
|
176
|
-
*
|
|
177
|
-
* REAL-WORLD ANALOGUE: Engineers who read the docs ship more confidently
|
|
178
|
-
* and more often than those who guess at the platform.
|
|
179
|
-
*
|
|
180
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
181
|
-
* 6. COST OVERRUN PATTERN (event — closure state)
|
|
182
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
183
|
-
*
|
|
184
|
-
* PATTERN: When cost_change_percent > 25 on a "cost report generated" event,
|
|
185
|
-
* the user is stored in a module-level Map. Their next "infrastructure scaled"
|
|
186
|
-
* event is forced to scale_direction = "down". No flag — discover by
|
|
187
|
-
* sequencing cost-report → infrastructure-scaled per user.
|
|
188
|
-
*
|
|
189
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
190
|
-
*
|
|
191
|
-
* Report 1: Scale Direction Distribution
|
|
192
|
-
* - Report type: Insights
|
|
193
|
-
* - Event: "infrastructure scaled"
|
|
194
|
-
* - Measure: Total
|
|
195
|
-
* - Breakdown: "scale_direction"
|
|
196
|
-
* - Expected: "down" share is elevated above the configured baseline
|
|
197
|
-
*
|
|
198
|
-
* Report 2: Sequencing Check
|
|
199
|
-
* - Inspect users with cost_change_percent > 25 on cost report;
|
|
200
|
-
* their next "infrastructure scaled" should be scale_direction="down"
|
|
201
|
-
* - Expected: ~100% match for the next-scale event after a cost spike
|
|
202
|
-
*
|
|
203
|
-
* REAL-WORLD ANALOGUE: A surprise cloud bill triggers an immediate
|
|
204
|
-
* downscale; no engineer ignores a 25% month-over-month cost jump.
|
|
205
|
-
*
|
|
206
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
207
|
-
* 7. FAILED DEPLOYMENT RECOVERY (everything)
|
|
208
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
209
|
-
*
|
|
210
|
-
* PATTERN: After a failed pipeline run, the user's next successful deploy has
|
|
211
|
-
* duration_sec * 1.5 (recovery deploys are slower). No flag — discover by
|
|
212
|
-
* sequencing failed → next-success pipeline events per user and comparing
|
|
213
|
-
* duration.
|
|
214
|
-
*
|
|
215
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
216
|
-
*
|
|
217
|
-
* Report 1: Pipeline Duration After Failure (sequencing query)
|
|
218
|
-
* - For each user, find runs where prior run was status="failed"
|
|
219
|
-
* - Compare avg duration_sec of those "next" runs vs all other successful runs
|
|
220
|
-
* - Expected: post-failure runs ~ 1.5x longer duration
|
|
221
|
-
*
|
|
222
|
-
* REAL-WORLD ANALOGUE: After a bad deploy, teams add manual gates and
|
|
223
|
-
* extra verification steps that slow the very next release.
|
|
224
|
-
*
|
|
225
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
226
|
-
* 8. ENTERPRISE VS STARTUP (user)
|
|
227
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
228
|
-
*
|
|
229
|
-
* PATTERN: Company size determines seat_count, annual_contract_value, and
|
|
230
|
-
* customer_success_manager (enterprise only). All users get a
|
|
231
|
-
* customer_health_score on the profile.
|
|
232
|
-
*
|
|
233
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
234
|
-
*
|
|
235
|
-
* Report 1: ACV by Company Size
|
|
236
|
-
* - Report type: Insights
|
|
237
|
-
* - Event: any event
|
|
238
|
-
* - Measure: Unique users
|
|
239
|
-
* - Breakdown: "company_size" (user property)
|
|
240
|
-
* - Expected: startup ($0-3.6K), smb ($3.6K-12K), mid_market ($12K-50K),
|
|
241
|
-
* enterprise ($50K-500K)
|
|
242
|
-
*
|
|
243
|
-
* Report 2: Seat Count by Company Size
|
|
244
|
-
* - Report type: Insights
|
|
245
|
-
* - Event: any event
|
|
246
|
-
* - Measure: Average of "seat_count" (user property)
|
|
247
|
-
* - Breakdown: "company_size"
|
|
248
|
-
* - Expected: monotonic ramp from startup to enterprise
|
|
249
|
-
*
|
|
250
|
-
* REAL-WORLD ANALOGUE: B2B SaaS pricing scales orders of magnitude across
|
|
251
|
-
* customer segments — from a $99/mo startup to a $500K Fortune 500 contract.
|
|
252
|
-
*
|
|
253
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
254
|
-
* 9. INCIDENT RESPONSE TTC (everything)
|
|
255
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
256
|
-
*
|
|
257
|
-
* PATTERN: Enterprise companies resolve incidents faster; startups resolve
|
|
258
|
-
* slower. The hook reads company_size from the user profile and scales
|
|
259
|
-
* response_time_mins (on "alert acknowledged") and resolution_time_mins
|
|
260
|
-
* (on "alert resolved") by a factor: enterprise 0.67x, startup 1.5x,
|
|
261
|
-
* smb/mid_market unchanged. Additionally, the first incident-response
|
|
262
|
-
* funnel sequence (alert triggered → alert acknowledged → alert resolved)
|
|
263
|
-
* has its inter-step timestamps scaled via scaleFunnelTTC with the same
|
|
264
|
-
* factor, so Mixpanel funnel TTC reports reflect the gap. This compounds
|
|
265
|
-
* with Hook 4 (integration users) — an enterprise user with both Slack
|
|
266
|
-
* and PagerDuty configured gets the fastest resolution times.
|
|
267
|
-
*
|
|
268
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
269
|
-
*
|
|
270
|
-
* Report 1: Avg Response Time by Company Size
|
|
271
|
-
* - Report type: Insights
|
|
272
|
-
* - Event: "alert acknowledged"
|
|
273
|
-
* - Measure: Average of "response_time_mins"
|
|
274
|
-
* - Breakdown: "company_size" (user property)
|
|
275
|
-
* - Expected: enterprise ~ 0.67x startup
|
|
276
|
-
*
|
|
277
|
-
* Report 2: Incident Funnel TTC by Company Size
|
|
278
|
-
* - Report type: Funnels
|
|
279
|
-
* - Steps: "alert triggered" → "alert acknowledged" → "alert resolved"
|
|
280
|
-
* - Measure: Median time to convert
|
|
281
|
-
* - Breakdown: "company_size"
|
|
282
|
-
* - Expected: enterprise ~ 0.67x median TTC vs startup ~ 1.5x
|
|
283
|
-
*
|
|
284
|
-
* Report 3: Avg Resolution Time by Company Size
|
|
285
|
-
* - Report type: Insights
|
|
286
|
-
* - Event: "alert resolved"
|
|
287
|
-
* - Measure: Average of "resolution_time_mins"
|
|
288
|
-
* - Breakdown: "company_size"
|
|
289
|
-
* - Expected: enterprise ~ 0.67x startup
|
|
290
|
-
*
|
|
291
|
-
* REAL-WORLD ANALOGUE: Enterprise teams have dedicated SRE rotations,
|
|
292
|
-
* automated runbooks, and premium support contracts that compress
|
|
293
|
-
* incident timelines. Startups rely on smaller teams with less tooling.
|
|
294
|
-
*
|
|
295
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
296
|
-
* 10. DOCS MAGIC NUMBER (everything)
|
|
297
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
298
|
-
*
|
|
299
|
-
* PATTERN: Users in the 4-7 documentation-viewed sweet spot get 2-3 extra
|
|
300
|
-
* production "service deployed" events cloned into their stream (boosting
|
|
301
|
-
* deploy frequency ~40%). Users with 8+ documentation views are over-
|
|
302
|
-
* engaged browsers; 25% of their "service deployed" events are dropped.
|
|
303
|
-
* No flag is stamped — discoverable only by binning users on doc-view
|
|
304
|
-
* count and comparing per-user deploy volume.
|
|
305
|
-
*
|
|
306
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
307
|
-
*
|
|
308
|
-
* Report 1: Deploys per User by Docs-View Bucket
|
|
309
|
-
* - Report type: Insights (with cohort)
|
|
310
|
-
* - Cohort A: users with 4-7 "documentation viewed" events
|
|
311
|
-
* - Cohort B: users with 0-3 "documentation viewed" events
|
|
312
|
-
* - Event: "service deployed"
|
|
313
|
-
* - Measure: Total per user
|
|
314
|
-
* - Expected: Cohort A ~ 1.4x deploys per user vs Cohort B
|
|
315
|
-
*
|
|
316
|
-
* Report 2: Deploys per User on Heavy Doc Readers
|
|
317
|
-
* - Report type: Insights (with cohort)
|
|
318
|
-
* - Cohort C: users with >= 8 "documentation viewed" events
|
|
319
|
-
* - Cohort A: users with 4-7
|
|
320
|
-
* - Event: "service deployed"
|
|
321
|
-
* - Measure: Total per user
|
|
322
|
-
* - Expected: Cohort C ~ 25% fewer deploys per user vs Cohort A
|
|
323
|
-
*
|
|
324
|
-
* REAL-WORLD ANALOGUE: Engineers who read just enough docs deploy with
|
|
325
|
-
* confidence; those who read excessively may be stuck troubleshooting
|
|
326
|
-
* and never ship, or are evaluating the product without committing.
|
|
327
|
-
*
|
|
328
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
329
|
-
* 11. DEPLOY PIPELINE EXPERIMENT (funnel experiment — engine-managed)
|
|
330
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
331
|
-
*
|
|
332
|
-
* PATTERN: The deployment funnel (deployment pipeline run → service deployed
|
|
333
|
-
* → dashboard viewed) runs a "Canary Deploys" experiment starting 45 days
|
|
334
|
-
* before dataset end. Users are randomly assigned to Control or "Canary
|
|
335
|
-
* Deploys" variant. The Canary variant gets 1.2x conversion multiplier and
|
|
336
|
-
* 0.85x time-to-convert multiplier (faster + higher conversion). The engine
|
|
337
|
-
* emits `$experiment_started` events with `Experiment Name` and
|
|
338
|
-
* `Variant Name` properties. No hook code needed — the engine handles
|
|
339
|
-
* variant assignment and conversion/TTC scaling.
|
|
340
|
-
*
|
|
341
|
-
* HOW TO FIND IT IN MIXPANEL:
|
|
342
|
-
*
|
|
343
|
-
* Report 1: Experiment Enrollment
|
|
344
|
-
* - Report type: Insights
|
|
345
|
-
* - Event: "$experiment_started"
|
|
346
|
-
* - Measure: Total
|
|
347
|
-
* - Breakdown: "Variant Name"
|
|
348
|
-
* - Expected: roughly even split between "Control" and "Canary Deploys"
|
|
349
|
-
*
|
|
350
|
-
* Report 2: Deploy Funnel by Variant
|
|
351
|
-
* - Report type: Funnels
|
|
352
|
-
* - Steps: "deployment pipeline run" → "service deployed" → "dashboard viewed"
|
|
353
|
-
* - Breakdown: "Variant Name" (user property)
|
|
354
|
-
* - Expected: Canary variant ~ 1.2x conversion vs Control
|
|
355
|
-
*
|
|
356
|
-
* Report 3: Deploy TTC by Variant
|
|
357
|
-
* - Report type: Funnels
|
|
358
|
-
* - Steps: same as above
|
|
359
|
-
* - Measure: Median time to convert
|
|
360
|
-
* - Breakdown: "Variant Name"
|
|
361
|
-
* - Expected: Canary variant ~ 0.85x TTC vs Control (faster)
|
|
362
|
-
*
|
|
363
|
-
* REAL-WORLD ANALOGUE: Teams A/B test canary deployment strategies.
|
|
364
|
-
* Canary deploys catch issues earlier, improving both success rate and
|
|
365
|
-
* deployment velocity.
|
|
366
|
-
*
|
|
367
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
368
|
-
* EXPECTED METRICS SUMMARY
|
|
369
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
370
|
-
*
|
|
371
|
-
* Hook | Metric | Baseline | Hook Effect | Ratio
|
|
372
|
-
* -------------------------|--------------------------|-----------|----------------|------
|
|
373
|
-
* End-of-Quarter Spike | Plan upgrades/day | ~2/day | ~8/day | 4x
|
|
374
|
-
* Churned Accounts | Users active month 2 | 100% | 90% | 0.9x
|
|
375
|
-
* Alert Escalation | Incidents from alerts | 0% | ~30% of crit | new
|
|
376
|
-
* Integration Users | MTTR (minutes) | ~300 | ~150 | 0.5x
|
|
377
|
-
* Docs Readers | Prod deploys/user | ~3 | ~5-6 | 1.8x
|
|
378
|
-
* Cost Overrun | Scale-down after overrun | 50% | 100% | 2x
|
|
379
|
-
* Failed Deploy Recovery | Deploy duration (sec) | ~500 | ~750 | 1.5x
|
|
380
|
-
* Enterprise vs Startup | ACV range | $0-3.6K | $50K-500K | 100x+
|
|
381
|
-
* Incident Response TTC | Enterprise response_time | 1x | 0.67x | -33%
|
|
382
|
-
* Incident Response TTC | Startup response_time | 1x | 1.5x | +50%
|
|
383
|
-
* Docs Magic Number | sweet (4-7) deploys/user | 1x | ~1.4x | +40%
|
|
384
|
-
* Docs Magic Number | over (8+) deploys/user | 1x | ~0.75x | -25%
|
|
385
|
-
* Deploy Experiment | Canary conversion | 65% | ~78% | 1.2x
|
|
386
|
-
* Deploy Experiment | Canary TTC | 1d | ~0.85d | 0.85x
|
|
387
|
-
*/
|
|
388
|
-
|
|
389
|
-
// ── SCALE ──
|
|
390
|
-
const SEED = "harness-sass";
|
|
391
|
-
const NUM_USERS = 10_000;
|
|
392
|
-
const DATASET_START = "2026-01-01T00:00:00Z";
|
|
393
|
-
const DATASET_END = "2026-05-01T23:59:59Z";
|
|
394
|
-
const EVENTS_PER_DAY = 1.2;
|
|
395
|
-
const token = process.env.MP_TOKEN || "your-mixpanel-token";
|
|
396
|
-
|
|
397
|
-
const chance = u.initChance(SEED);
|
|
398
|
-
|
|
399
|
-
// ── KNOBS (tweak these to reshape stories) ──
|
|
400
|
-
const EOQ_START_DAY = 100;
|
|
401
|
-
const EOQ_END_DAY = 110;
|
|
402
|
-
const EOQ_UPGRADE_LIKELIHOOD = 40;
|
|
403
|
-
const EOQ_INVITE_CLONE_LIKELIHOOD = 50;
|
|
404
|
-
|
|
405
|
-
const CHURN_USER_HASH_MOD = 5;
|
|
406
|
-
const CHURN_CUTOFF_DAYS = 30;
|
|
407
|
-
|
|
408
|
-
const ALERT_ESCALATION_LIKELIHOOD = 30;
|
|
409
|
-
|
|
410
|
-
const INTEGRATION_RESPONSE_FACTOR = 0.4;
|
|
411
|
-
const INTEGRATION_RESOLUTION_FACTOR = 0.5;
|
|
412
|
-
|
|
413
|
-
const COST_OVERRUN_THRESHOLD = 25;
|
|
414
|
-
|
|
415
|
-
const FAILED_DEPLOY_RECOVERY_MULT = 1.5;
|
|
416
|
-
|
|
417
|
-
const DOCS_SWEET_MIN = 4;
|
|
418
|
-
const DOCS_SWEET_MAX = 7;
|
|
419
|
-
const DOCS_OVER_THRESHOLD = 8;
|
|
420
|
-
const DOCS_EXTRA_DEPLOYS_MIN = 2;
|
|
421
|
-
const DOCS_EXTRA_DEPLOYS_MAX = 3;
|
|
422
|
-
const DOCS_DEPLOY_DROP_LIKELIHOOD = 25;
|
|
423
|
-
|
|
424
|
-
const TTC_ENTERPRISE_FACTOR = 0.67;
|
|
425
|
-
const TTC_STARTUP_FACTOR = 1.5;
|
|
426
|
-
const INCIDENT_SEQ_WINDOW_MINS = 60 * 24 * 30;
|
|
427
|
-
|
|
428
|
-
// ── DATA ARRAYS ──
|
|
429
|
-
const serviceIds = v.range(1, 201).map(() => `svc_${v.uid(8)}`);
|
|
430
|
-
const alertIds = v.range(1, 501).map(() => `alert_${v.uid(6)}`);
|
|
431
|
-
const pipelineIds = v.range(1, 101).map(() => `pipe_${v.uid(6)}`);
|
|
432
|
-
const runbookIds = v.range(1, 51).map(() => `rb_${v.uid(6)}`);
|
|
433
|
-
|
|
434
|
-
// ── HOOK STATE ──
|
|
435
|
-
// Module-level Map for closure-based state tracking across event-hook calls
|
|
436
|
-
const costOverrunUsers = new Map();
|
|
437
|
-
|
|
438
|
-
// ── HELPER FUNCTIONS ──
|
|
439
|
-
function handleEventHooks(record) {
|
|
440
|
-
// H3: ALERT ESCALATION REPLACEMENT — critical/emergency alerts sometimes
|
|
441
|
-
// become incident-created events.
|
|
442
|
-
if (record.event === "alert triggered") {
|
|
443
|
-
const severity = record.severity;
|
|
444
|
-
if ((severity === "critical" || severity === "emergency") && chance.bool({ likelihood: ALERT_ESCALATION_LIKELIHOOD })) {
|
|
445
|
-
return {
|
|
446
|
-
...record,
|
|
447
|
-
event: "incident created",
|
|
448
|
-
escalation_level: chance.pickone(["P1", "P2"]),
|
|
449
|
-
teams_paged: chance.integer({ min: 1, max: 5 }),
|
|
450
|
-
incident_id: `inc_${v.uid(8)}`,
|
|
451
|
-
original_severity: severity,
|
|
452
|
-
original_alert_type: record.alert_type,
|
|
453
|
-
auto_escalated: true,
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// H6: COST OVERRUN PATTERN — cost reports with cost_change > 25% record
|
|
459
|
-
// the user, then the next infrastructure-scaled event from that user is
|
|
460
|
-
// forced to scale_direction = "down".
|
|
461
|
-
if (record.event === "cost report generated" && record.cost_change_percent > COST_OVERRUN_THRESHOLD) {
|
|
462
|
-
costOverrunUsers.set(record.user_id, true);
|
|
463
|
-
}
|
|
464
|
-
if (record.event === "infrastructure scaled" && costOverrunUsers.has(record.user_id)) {
|
|
465
|
-
record.scale_direction = "down";
|
|
466
|
-
costOverrunUsers.delete(record.user_id);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return record;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
function handleUserHooks(record) {
|
|
473
|
-
// H8: ENTERPRISE VS STARTUP — company size determines seat count, ACV,
|
|
474
|
-
// and CSM. Real profile attrs.
|
|
475
|
-
const companySize = record.company_size;
|
|
476
|
-
if (companySize === "enterprise") {
|
|
477
|
-
record.seat_count = chance.integer({ min: 50, max: 500 });
|
|
478
|
-
record.annual_contract_value = chance.integer({ min: 50000, max: 500000 });
|
|
479
|
-
record.customer_success_manager = true;
|
|
480
|
-
} else if (companySize === "mid_market") {
|
|
481
|
-
record.seat_count = chance.integer({ min: 10, max: 50 });
|
|
482
|
-
record.annual_contract_value = chance.integer({ min: 12000, max: 50000 });
|
|
483
|
-
record.customer_success_manager = false;
|
|
484
|
-
} else if (companySize === "smb") {
|
|
485
|
-
record.seat_count = chance.integer({ min: 3, max: 10 });
|
|
486
|
-
record.annual_contract_value = chance.integer({ min: 3600, max: 12000 });
|
|
487
|
-
record.customer_success_manager = false;
|
|
488
|
-
} else if (companySize === "startup") {
|
|
489
|
-
record.seat_count = chance.integer({ min: 1, max: 5 });
|
|
490
|
-
record.annual_contract_value = chance.integer({ min: 0, max: 3600 });
|
|
491
|
-
record.customer_success_manager = false;
|
|
492
|
-
}
|
|
493
|
-
record.customer_health_score = chance.integer({ min: 1, max: 100 });
|
|
494
|
-
return record;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
function handleEverythingHooks(record, meta) {
|
|
498
|
-
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
499
|
-
const userEvents = record;
|
|
500
|
-
const profile = meta.profile;
|
|
501
|
-
|
|
502
|
-
userEvents.forEach(e => {
|
|
503
|
-
e.plan_tier = profile.plan_tier;
|
|
504
|
-
e.cloud_provider = profile.cloud_provider;
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
// H1a: END-OF-QUARTER SPIKE — days 100-110, billing events flip
|
|
508
|
-
// event_type to plan_upgraded 40% of the time.
|
|
509
|
-
userEvents.forEach(e => {
|
|
510
|
-
if (e.event !== "billing event") return;
|
|
511
|
-
const dayInDataset = dayjs(e.time).diff(datasetStart, "days", true);
|
|
512
|
-
if (dayInDataset >= EOQ_START_DAY && dayInDataset <= EOQ_END_DAY && chance.bool({ likelihood: EOQ_UPGRADE_LIKELIHOOD })) {
|
|
513
|
-
e.event_type = "plan_upgraded";
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
// H1b: END-OF-QUARTER TEAM INVITE SPIKE — days 100-110, clone 50% of
|
|
518
|
-
// team-member-invited events (push, not return).
|
|
519
|
-
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
520
|
-
const e = userEvents[i];
|
|
521
|
-
if (e.event !== "team member invited") continue;
|
|
522
|
-
const dayInDataset = dayjs(e.time).diff(datasetStart, "days", true);
|
|
523
|
-
if (dayInDataset >= EOQ_START_DAY && dayInDataset <= EOQ_END_DAY && chance.bool({ likelihood: EOQ_INVITE_CLONE_LIKELIHOOD })) {
|
|
524
|
-
userEvents.push({
|
|
525
|
-
...e,
|
|
526
|
-
time: dayjs(e.time).add(chance.integer({ min: 1, max: 60 }), "minutes").toISOString(),
|
|
527
|
-
user_id: e.user_id,
|
|
528
|
-
role: chance.pickone(["editor", "viewer"]),
|
|
529
|
-
invitation_method: chance.pickone(["email", "sso", "slack"]),
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// H2: CHURNED ACCOUNT SILENCING — ~20% of users (hash %5) have post-day-30
|
|
535
|
-
// events removed.
|
|
536
|
-
if (userEvents && userEvents.length > 0) {
|
|
537
|
-
const firstEvent = userEvents[0];
|
|
538
|
-
const idHash = String(firstEvent.user_id || firstEvent.device_id).split("").reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
|
539
|
-
if ((idHash % CHURN_USER_HASH_MOD) === 0) {
|
|
540
|
-
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
541
|
-
const dayInDataset = dayjs(userEvents[i].time).diff(datasetStart, "days", true);
|
|
542
|
-
if (dayInDataset > CHURN_CUTOFF_DAYS) {
|
|
543
|
-
userEvents.splice(i, 1);
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
// H4: INTEGRATION USERS SUCCEED — Slack+PagerDuty users get alert
|
|
550
|
-
// response_time_mins 0.4x and resolution_time_mins 0.5x.
|
|
551
|
-
let hasSlack = false;
|
|
552
|
-
let hasPagerduty = false;
|
|
553
|
-
userEvents.forEach((event) => {
|
|
554
|
-
if (event.event === "integration configured") {
|
|
555
|
-
if (event.integration_type === "slack") hasSlack = true;
|
|
556
|
-
if (event.integration_type === "pagerduty") hasPagerduty = true;
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
if (hasSlack && hasPagerduty) {
|
|
560
|
-
userEvents.forEach((event) => {
|
|
561
|
-
if (event.event === "alert acknowledged" && event.response_time_mins) {
|
|
562
|
-
event.response_time_mins = Math.floor(event.response_time_mins * INTEGRATION_RESPONSE_FACTOR);
|
|
563
|
-
}
|
|
564
|
-
if (event.event === "alert resolved" && event.resolution_time_mins) {
|
|
565
|
-
event.resolution_time_mins = Math.floor(event.resolution_time_mins * INTEGRATION_RESOLUTION_FACTOR);
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// H5 + H10: DOCS MAGIC NUMBER — sweet 4-7 docs → +40% extra cloned
|
|
571
|
-
// service-deployed events; over 8+ → drop 25% of service-deployed events.
|
|
572
|
-
const docsCount = userEvents.filter(e => e.event === "documentation viewed").length;
|
|
573
|
-
const deployTemplate = userEvents.find(e => e.event === "service deployed");
|
|
574
|
-
if (docsCount >= DOCS_SWEET_MIN && docsCount <= DOCS_SWEET_MAX && deployTemplate) {
|
|
575
|
-
const lastEvent = userEvents[userEvents.length - 1];
|
|
576
|
-
const extraDeploys = chance.integer({ min: DOCS_EXTRA_DEPLOYS_MIN, max: DOCS_EXTRA_DEPLOYS_MAX });
|
|
577
|
-
for (let i = 0; i < extraDeploys; i++) {
|
|
578
|
-
userEvents.push({
|
|
579
|
-
...deployTemplate,
|
|
580
|
-
time: dayjs(lastEvent.time).add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
|
|
581
|
-
user_id: lastEvent.user_id,
|
|
582
|
-
service_id: chance.pickone(serviceIds),
|
|
583
|
-
service_type: chance.pickone(["web_app", "api", "database", "cache", "queue", "ml_model"]),
|
|
584
|
-
environment: "production",
|
|
585
|
-
cloud_provider: profile.cloud_provider,
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
} else if (docsCount >= DOCS_OVER_THRESHOLD) {
|
|
589
|
-
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
590
|
-
if (userEvents[i].event === "service deployed" && chance.bool({ likelihood: DOCS_DEPLOY_DROP_LIKELIHOOD })) {
|
|
591
|
-
userEvents.splice(i, 1);
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// H7: FAILED DEPLOYMENT RECOVERY — find failed→success pairs in this
|
|
597
|
-
// user's pipeline events, multiply duration_sec by 1.5 on the recovery
|
|
598
|
-
// deploy.
|
|
599
|
-
const pipelineEvents = userEvents
|
|
600
|
-
.filter(e => e.event === "deployment pipeline run")
|
|
601
|
-
.sort((a, b) => a.time.localeCompare(b.time));
|
|
602
|
-
for (let i = 1; i < pipelineEvents.length; i++) {
|
|
603
|
-
if (pipelineEvents[i - 1].status === "failed" && pipelineEvents[i].status === "success") {
|
|
604
|
-
pipelineEvents[i].duration_sec = Math.floor((pipelineEvents[i].duration_sec || 300) * FAILED_DEPLOY_RECOVERY_MULT);
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// H9: INCIDENT RESPONSE TTC — enterprise resolves faster, startup
|
|
609
|
-
// resolves slower. Scale response_time_mins on acknowledged events and
|
|
610
|
-
// resolution_time_mins on resolved events by company_size, and shift the
|
|
611
|
-
// first incident-response funnel sequence timestamps for funnel TTC
|
|
612
|
-
// reports. Compounds with H4 (integration users).
|
|
613
|
-
const companySegment = profile?.company_size;
|
|
614
|
-
const ttcFactor = (
|
|
615
|
-
companySegment === "enterprise" ? TTC_ENTERPRISE_FACTOR :
|
|
616
|
-
companySegment === "startup" ? TTC_STARTUP_FACTOR :
|
|
617
|
-
1.0
|
|
618
|
-
);
|
|
619
|
-
if (ttcFactor !== 1.0) {
|
|
620
|
-
// Timestamp shift: affects Mixpanel funnel TTC
|
|
621
|
-
const incidentSeq = findFirstSequence(
|
|
622
|
-
userEvents,
|
|
623
|
-
["alert triggered", "alert acknowledged", "alert resolved"],
|
|
624
|
-
INCIDENT_SEQ_WINDOW_MINS
|
|
625
|
-
);
|
|
626
|
-
if (incidentSeq) scaleFunnelTTC(incidentSeq, ttcFactor);
|
|
627
|
-
// Property scale: affects Insights AVG reports
|
|
628
|
-
userEvents.forEach(e => {
|
|
629
|
-
if (e.event === "alert acknowledged" && e.response_time_mins) {
|
|
630
|
-
e.response_time_mins = Math.max(1, Math.round(e.response_time_mins * ttcFactor));
|
|
631
|
-
}
|
|
632
|
-
if (e.event === "alert resolved" && e.resolution_time_mins) {
|
|
633
|
-
e.resolution_time_mins = Math.max(1, Math.round(e.resolution_time_mins * ttcFactor));
|
|
634
|
-
}
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
return record;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// ── CONFIG ──
|
|
642
|
-
/** @type {Config} */
|
|
643
|
-
const config = {
|
|
644
|
-
seed: SEED,
|
|
645
|
-
datasetStart: DATASET_START,
|
|
646
|
-
datasetEnd: DATASET_END,
|
|
647
|
-
avgEventsPerUserPerDay: EVENTS_PER_DAY,
|
|
648
|
-
numUsers: NUM_USERS,
|
|
649
|
-
format: "json",
|
|
650
|
-
gzip: true,
|
|
651
|
-
credentials: {
|
|
652
|
-
token,
|
|
653
|
-
},
|
|
654
|
-
switches: {
|
|
655
|
-
hasSessionIds: true,
|
|
656
|
-
alsoInferFunnels: false,
|
|
657
|
-
hasLocation: true,
|
|
658
|
-
hasAndroidDevices: false,
|
|
659
|
-
hasIOSDevices: false,
|
|
660
|
-
hasDesktopDevices: true,
|
|
661
|
-
hasBrowser: true,
|
|
662
|
-
hasCampaigns: false,
|
|
663
|
-
isAnonymous: false,
|
|
664
|
-
hasAdSpend: false,
|
|
665
|
-
hasAvatar: true,
|
|
666
|
-
},
|
|
667
|
-
// Phase 2 identity model — B2B SaaS reference. Engineers commonly use 1-2
|
|
668
|
-
// devices (desktop + work laptop). avgDevicePerUser:2 puts a meaningful
|
|
669
|
-
// per-session sticky-device pattern in Mixpanel device dashboards.
|
|
670
|
-
identity: {
|
|
671
|
-
avgDevicePerUser: 2,
|
|
672
|
-
},
|
|
673
|
-
concurrency: 1,
|
|
674
|
-
writeToDisk: false,
|
|
675
|
-
scdProps: {
|
|
676
|
-
primary_role: {
|
|
677
|
-
values: ["viewer", "editor", "admin", "owner"],
|
|
678
|
-
frequency: "month",
|
|
679
|
-
timing: "fuzzy",
|
|
680
|
-
max: 6
|
|
681
|
-
},
|
|
682
|
-
plan_tier: {
|
|
683
|
-
values: ["starter", "growth", "enterprise", "scale"],
|
|
684
|
-
frequency: "month",
|
|
685
|
-
timing: "fixed",
|
|
686
|
-
max: 6,
|
|
687
|
-
type: "company_id"
|
|
688
|
-
}
|
|
689
|
-
},
|
|
690
|
-
|
|
691
|
-
funnels: [
|
|
692
|
-
{
|
|
693
|
-
// First funnel — `workspace created` is the auth event for B2B users.
|
|
694
|
-
// Models real B2B onboarding: most teams take ≤1 retry before sticking.
|
|
695
|
-
sequence: ["workspace created", "service deployed", "dashboard viewed"],
|
|
696
|
-
isFirstFunnel: true,
|
|
697
|
-
conversionRate: 70,
|
|
698
|
-
timeToConvert: 2,
|
|
699
|
-
attempts: { min: 0, max: 1 },
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
// Daily monitoring: dashboards, queries, API calls (most common)
|
|
703
|
-
sequence: ["dashboard viewed", "query executed", "api call"],
|
|
704
|
-
conversionRate: 80,
|
|
705
|
-
timeToConvert: 0.5,
|
|
706
|
-
weight: 5,
|
|
707
|
-
},
|
|
708
|
-
{
|
|
709
|
-
// Incident response pipeline
|
|
710
|
-
sequence: ["alert triggered", "alert acknowledged", "alert resolved"],
|
|
711
|
-
conversionRate: 55,
|
|
712
|
-
timeToConvert: 6,
|
|
713
|
-
weight: 4,
|
|
714
|
-
},
|
|
715
|
-
{
|
|
716
|
-
// Deployment cycle
|
|
717
|
-
sequence: ["deployment pipeline run", "service deployed", "dashboard viewed"],
|
|
718
|
-
conversionRate: 65,
|
|
719
|
-
timeToConvert: 1,
|
|
720
|
-
weight: 3,
|
|
721
|
-
experiment: {
|
|
722
|
-
name: "Canary Deploys",
|
|
723
|
-
variants: [
|
|
724
|
-
{ name: "Control" },
|
|
725
|
-
{ name: "Canary Deploys", conversionMultiplier: 1.2, ttcMultiplier: 0.85 },
|
|
726
|
-
],
|
|
727
|
-
startDaysBeforeEnd: 45,
|
|
728
|
-
},
|
|
729
|
-
},
|
|
730
|
-
{
|
|
731
|
-
// Infrastructure management
|
|
732
|
-
sequence: ["cost report generated", "infrastructure scaled", "security scan"],
|
|
733
|
-
conversionRate: 50,
|
|
734
|
-
timeToConvert: 4,
|
|
735
|
-
weight: 2,
|
|
736
|
-
},
|
|
737
|
-
{
|
|
738
|
-
// Team and config management
|
|
739
|
-
sequence: ["team member invited", "integration configured", "feature flag toggled"],
|
|
740
|
-
conversionRate: 40,
|
|
741
|
-
timeToConvert: 8,
|
|
742
|
-
weight: 2,
|
|
743
|
-
},
|
|
744
|
-
{
|
|
745
|
-
// Documentation and runbook usage
|
|
746
|
-
sequence: ["documentation viewed", "runbook executed", "service deployed"],
|
|
747
|
-
conversionRate: 45,
|
|
748
|
-
timeToConvert: 2,
|
|
749
|
-
weight: 2,
|
|
750
|
-
},
|
|
751
|
-
{
|
|
752
|
-
// Billing and account management
|
|
753
|
-
sequence: ["billing event", "dashboard viewed"],
|
|
754
|
-
conversionRate: 60,
|
|
755
|
-
timeToConvert: 1,
|
|
756
|
-
weight: 1,
|
|
757
|
-
},
|
|
758
|
-
],
|
|
759
|
-
|
|
760
|
-
events: [
|
|
761
|
-
{
|
|
762
|
-
event: "workspace created",
|
|
763
|
-
weight: 1,
|
|
764
|
-
isFirstEvent: true,
|
|
765
|
-
// Phase 2 identity: workspace creation is the B2B equivalent of Sign Up
|
|
766
|
-
// — engine stamps user_id+device_id on this event when it fires inside
|
|
767
|
-
// the user's first funnel.
|
|
768
|
-
isAuthEvent: true,
|
|
769
|
-
properties: {
|
|
770
|
-
company_size: ["startup", "smb", "mid_market", "enterprise"],
|
|
771
|
-
industry: ["tech", "finance", "healthcare", "retail", "media"],
|
|
772
|
-
}
|
|
773
|
-
},
|
|
774
|
-
{
|
|
775
|
-
event: "service deployed",
|
|
776
|
-
weight: 10,
|
|
777
|
-
isStrictEvent: false,
|
|
778
|
-
properties: {
|
|
779
|
-
service_id: serviceIds,
|
|
780
|
-
service_type: ["web_app", "api", "database", "cache", "queue", "ml_model"],
|
|
781
|
-
environment: ["production", "staging", "dev"],
|
|
782
|
-
cloud_provider: ["aws", "gcp", "azure"],
|
|
783
|
-
}
|
|
784
|
-
},
|
|
785
|
-
{
|
|
786
|
-
event: "dashboard viewed",
|
|
787
|
-
weight: 20,
|
|
788
|
-
isStrictEvent: false,
|
|
789
|
-
properties: {
|
|
790
|
-
dashboard_type: ["overview", "cost", "performance", "security", "custom"],
|
|
791
|
-
time_range: ["1h", "6h", "24h", "7d", "30d"],
|
|
792
|
-
}
|
|
793
|
-
},
|
|
794
|
-
{
|
|
795
|
-
event: "alert triggered",
|
|
796
|
-
weight: 12,
|
|
797
|
-
isStrictEvent: false,
|
|
798
|
-
properties: {
|
|
799
|
-
alert_id: alertIds,
|
|
800
|
-
severity: ["info", "warning", "critical", "emergency"],
|
|
801
|
-
alert_type: ["cpu", "memory", "latency", "error_rate", "disk", "network"],
|
|
802
|
-
service_id: serviceIds,
|
|
803
|
-
}
|
|
804
|
-
},
|
|
805
|
-
{
|
|
806
|
-
event: "incident created",
|
|
807
|
-
weight: 1,
|
|
808
|
-
properties: {
|
|
809
|
-
escalation_level: ["P1", "P2"],
|
|
810
|
-
teams_paged: u.weighNumRange(1, 5),
|
|
811
|
-
incident_id: () => `inc_${v.uid(8)}`,
|
|
812
|
-
original_severity: ["critical", "emergency"],
|
|
813
|
-
original_alert_type: ["cpu", "memory", "latency", "error_rate", "disk", "network"],
|
|
814
|
-
service_id: serviceIds,
|
|
815
|
-
auto_escalated: [true],
|
|
816
|
-
}
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
event: "alert acknowledged",
|
|
820
|
-
weight: 8,
|
|
821
|
-
isStrictEvent: false,
|
|
822
|
-
properties: {
|
|
823
|
-
alert_id: alertIds,
|
|
824
|
-
response_time_mins: u.weighNumRange(1, 120),
|
|
825
|
-
acknowledged_by_role: ["engineer", "sre", "manager", "oncall"],
|
|
826
|
-
}
|
|
827
|
-
},
|
|
828
|
-
{
|
|
829
|
-
event: "alert resolved",
|
|
830
|
-
weight: 7,
|
|
831
|
-
isStrictEvent: false,
|
|
832
|
-
properties: {
|
|
833
|
-
alert_id: alertIds,
|
|
834
|
-
resolution_time_mins: u.weighNumRange(5, 1440),
|
|
835
|
-
root_cause: ["config_change", "capacity", "bug", "dependency", "network"],
|
|
836
|
-
}
|
|
837
|
-
},
|
|
838
|
-
{
|
|
839
|
-
event: "deployment pipeline run",
|
|
840
|
-
weight: 9,
|
|
841
|
-
isStrictEvent: false,
|
|
842
|
-
properties: {
|
|
843
|
-
pipeline_id: pipelineIds,
|
|
844
|
-
status: ["success", "failed", "cancelled"],
|
|
845
|
-
duration_sec: u.weighNumRange(30, 1800),
|
|
846
|
-
commit_count: u.weighNumRange(1, 20),
|
|
847
|
-
}
|
|
848
|
-
},
|
|
849
|
-
{
|
|
850
|
-
event: "infrastructure scaled",
|
|
851
|
-
weight: 5,
|
|
852
|
-
isStrictEvent: false,
|
|
853
|
-
properties: {
|
|
854
|
-
service_id: serviceIds,
|
|
855
|
-
scale_direction: ["up", "up", "up", "down"],
|
|
856
|
-
previous_capacity: u.weighNumRange(1, 100),
|
|
857
|
-
new_capacity: u.weighNumRange(1, 100),
|
|
858
|
-
auto_scaled: [false, false, false, false, false, false, true],
|
|
859
|
-
}
|
|
860
|
-
},
|
|
861
|
-
{
|
|
862
|
-
event: "cost report generated",
|
|
863
|
-
weight: 4,
|
|
864
|
-
isStrictEvent: false,
|
|
865
|
-
properties: {
|
|
866
|
-
report_period: ["daily", "weekly", "monthly"],
|
|
867
|
-
total_cost: u.weighNumRange(100, 50000),
|
|
868
|
-
cost_change_percent: u.weighNumRange(-30, 50),
|
|
869
|
-
}
|
|
870
|
-
},
|
|
871
|
-
{
|
|
872
|
-
event: "team member invited",
|
|
873
|
-
weight: 3,
|
|
874
|
-
isStrictEvent: false,
|
|
875
|
-
properties: {
|
|
876
|
-
role: ["admin", "editor", "viewer", "billing"],
|
|
877
|
-
invitation_method: ["email", "sso", "slack"],
|
|
878
|
-
}
|
|
879
|
-
},
|
|
880
|
-
{
|
|
881
|
-
event: "integration configured",
|
|
882
|
-
weight: 4,
|
|
883
|
-
isStrictEvent: false,
|
|
884
|
-
properties: {
|
|
885
|
-
integration_type: ["slack", "pagerduty", "jira", "github", "datadog", "terraform"],
|
|
886
|
-
status: ["active", "paused", "error"],
|
|
887
|
-
}
|
|
888
|
-
},
|
|
889
|
-
{
|
|
890
|
-
event: "query executed",
|
|
891
|
-
weight: 15,
|
|
892
|
-
properties: {
|
|
893
|
-
query_type: ["metrics", "logs", "traces"],
|
|
894
|
-
time_range_hours: u.weighNumRange(1, 720),
|
|
895
|
-
result_count: u.weighNumRange(0, 10000),
|
|
896
|
-
}
|
|
897
|
-
},
|
|
898
|
-
{
|
|
899
|
-
event: "runbook executed",
|
|
900
|
-
weight: 3,
|
|
901
|
-
properties: {
|
|
902
|
-
runbook_id: runbookIds,
|
|
903
|
-
trigger: ["manual", "automated", "alert_triggered"],
|
|
904
|
-
success: [false, false, false, false, false, false, true],
|
|
905
|
-
}
|
|
906
|
-
},
|
|
907
|
-
{
|
|
908
|
-
event: "billing event",
|
|
909
|
-
weight: 3,
|
|
910
|
-
isStrictEvent: false,
|
|
911
|
-
properties: {
|
|
912
|
-
event_type: ["invoice_generated", "invoice_generated", "payment_received", "payment_received", "payment_received", "payment_failed", "plan_upgraded", "plan_downgraded"],
|
|
913
|
-
amount: u.weighNumRange(99, 25000),
|
|
914
|
-
}
|
|
915
|
-
},
|
|
916
|
-
{
|
|
917
|
-
event: "security scan",
|
|
918
|
-
weight: 6,
|
|
919
|
-
properties: {
|
|
920
|
-
scan_type: ["vulnerability", "compliance", "access_audit"],
|
|
921
|
-
findings_count: u.weighNumRange(0, 50),
|
|
922
|
-
critical_findings: u.weighNumRange(0, 10),
|
|
923
|
-
}
|
|
924
|
-
},
|
|
925
|
-
{
|
|
926
|
-
event: "api call",
|
|
927
|
-
weight: 16,
|
|
928
|
-
properties: {
|
|
929
|
-
endpoint: ["/deploy", "/status", "/metrics", "/alerts", "/config", "/billing"],
|
|
930
|
-
method: ["GET", "POST", "PUT", "DELETE"],
|
|
931
|
-
response_time_ms: u.weighNumRange(10, 5000),
|
|
932
|
-
status_code: [200, 201, 400, 401, 403, 500, 503],
|
|
933
|
-
}
|
|
934
|
-
},
|
|
935
|
-
{
|
|
936
|
-
event: "documentation viewed",
|
|
937
|
-
weight: 7,
|
|
938
|
-
isStrictEvent: false,
|
|
939
|
-
properties: {
|
|
940
|
-
doc_section: ["getting_started", "api_reference", "best_practices", "troubleshooting", "changelog"],
|
|
941
|
-
time_on_page_sec: u.weighNumRange(5, 600),
|
|
942
|
-
}
|
|
943
|
-
},
|
|
944
|
-
{
|
|
945
|
-
event: "feature flag toggled",
|
|
946
|
-
weight: 4,
|
|
947
|
-
properties: {
|
|
948
|
-
flag_name: () => `flag_${chance.word()}`,
|
|
949
|
-
new_state: ["disabled", "disabled", "disabled", "disabled", "disabled", "disabled", "enabled"],
|
|
950
|
-
environment: ["production", "staging", "dev"],
|
|
951
|
-
}
|
|
952
|
-
},
|
|
953
|
-
],
|
|
954
|
-
|
|
955
|
-
superProps: {
|
|
956
|
-
plan_tier: ["free", "free", "team", "team", "business", "enterprise"],
|
|
957
|
-
cloud_provider: ["aws", "gcp", "azure", "multi_cloud"],
|
|
958
|
-
},
|
|
959
|
-
|
|
960
|
-
userProps: {
|
|
961
|
-
company_size: ["startup", "startup", "smb", "mid_market", "enterprise"],
|
|
962
|
-
primary_role: ["engineer", "sre", "devops", "manager", "executive"],
|
|
963
|
-
team_name: ["Platform", "Backend", "Frontend", "Data", "Security", "Infrastructure"],
|
|
964
|
-
seat_count: [1],
|
|
965
|
-
annual_contract_value: [0],
|
|
966
|
-
customer_success_manager: [false],
|
|
967
|
-
customer_health_score: u.weighNumRange(1, 100),
|
|
968
|
-
plan_tier: ["free", "free", "team", "team", "business", "enterprise"],
|
|
969
|
-
cloud_provider: ["aws", "gcp", "azure", "multi_cloud"],
|
|
970
|
-
},
|
|
971
|
-
|
|
972
|
-
groupKeys: [
|
|
973
|
-
["company_id", 300, ["workspace created", "service deployed", "billing event", "team member invited"]],
|
|
974
|
-
],
|
|
975
|
-
|
|
976
|
-
groupProps: {
|
|
977
|
-
company_id: {
|
|
978
|
-
name: () => `${chance.word({ capitalize: true })} ${chance.pickone(["Systems", "Technologies", "Labs", "Cloud", "Digital", "Networks", "Solutions"])}`,
|
|
979
|
-
industry: ["tech", "finance", "healthcare", "retail", "media", "manufacturing", "logistics"],
|
|
980
|
-
employee_count: ["1-10", "11-50", "51-200", "201-1000", "1001-5000", "5000+"],
|
|
981
|
-
arr_bucket: ["<10k", "10k-50k", "50k-200k", "200k-1M", "1M+"],
|
|
982
|
-
}
|
|
983
|
-
},
|
|
984
|
-
|
|
985
|
-
lookupTables: [],
|
|
986
|
-
|
|
987
|
-
hook(record, type, meta) {
|
|
988
|
-
if (type === "event") return handleEventHooks(record);
|
|
989
|
-
if (type === "user") return handleUserHooks(record);
|
|
990
|
-
if (type === "everything") return handleEverythingHooks(record, meta);
|
|
991
|
-
return record;
|
|
992
|
-
}
|
|
993
|
-
};
|
|
994
|
-
|
|
995
|
-
export default config;
|