@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.
Files changed (146) hide show
  1. package/.claude/skills/analyze-soup/SKILL.md +21 -12
  2. package/.claude/skills/create-dungeon/SKILL.md +65 -5
  3. package/.claude/skills/create-project/SKILL.md +96 -0
  4. package/.claude/skills/create-project/provision.mjs +336 -0
  5. package/.claude/skills/verify-dungeon/SKILL.md +56 -25
  6. package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
  7. package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
  8. package/.claude/skills/write-hooks/SKILL.md +156 -18
  9. package/CHANGELOG.md +211 -0
  10. package/HOOKS.md +566 -112
  11. package/README.md +28 -9
  12. package/docs/guides/1.3.0-upgrade-guide.md +262 -0
  13. package/docs/guides/1.3.1-upgrade-guide.md +328 -0
  14. package/docs/guides/1.4.0-upgrade-guide.md +130 -0
  15. package/docs/guides/1.4.1-upgrade-guide.md +125 -0
  16. package/docs/guides/1.4.3-upgrade-guide.md +200 -0
  17. package/docs/guides/1.4.4-upgrade-guide.md +95 -0
  18. package/docs/guides/1.5.0-upgrade-guide.md +426 -0
  19. package/docs/guides/1.5.1-upgrade-guide.md +178 -0
  20. package/docs/guides/1.5.3-upgrade-guide.md +117 -0
  21. package/docs/guides/1.6.0-upgrade-guide.md +204 -0
  22. package/dungeons/technical/stories-verify.js +152 -0
  23. package/dungeons/vertical/README.md +154 -0
  24. package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
  25. package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
  26. package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
  27. package/dungeons/vertical/community/community.js +1589 -0
  28. package/dungeons/vertical/community/community.sql +216 -0
  29. package/dungeons/vertical/community/community.verify.mjs +72 -0
  30. package/dungeons/vertical/crypto/crypto.js +1483 -0
  31. package/dungeons/vertical/crypto/crypto.sql +194 -0
  32. package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
  33. package/dungeons/vertical/dating/dating.js +1540 -0
  34. package/dungeons/vertical/dating/dating.sql +288 -0
  35. package/dungeons/vertical/dating/dating.verify.mjs +72 -0
  36. package/dungeons/vertical/devtools/devtools.js +1524 -0
  37. package/dungeons/vertical/devtools/devtools.sql +161 -0
  38. package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
  39. package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
  40. package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
  41. package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
  42. package/dungeons/vertical/education/education.js +1763 -0
  43. package/dungeons/vertical/education/education.sql +243 -0
  44. package/dungeons/vertical/education/education.verify.mjs +75 -0
  45. package/dungeons/vertical/fintech/fintech.js +1410 -0
  46. package/dungeons/vertical/fintech/fintech.sql +175 -0
  47. package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
  48. package/dungeons/vertical/fitness/fitness.js +1495 -0
  49. package/dungeons/vertical/fitness/fitness.sql +206 -0
  50. package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
  51. package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
  52. package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
  53. package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
  54. package/dungeons/vertical/gaming/gaming.js +1741 -0
  55. package/dungeons/vertical/gaming/gaming.sql +249 -0
  56. package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
  57. package/dungeons/vertical/healthcare/healthcare.js +1488 -0
  58. package/dungeons/vertical/healthcare/healthcare.sql +193 -0
  59. package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
  60. package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
  61. package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
  62. package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
  63. package/dungeons/vertical/logistics/logistics.js +1541 -0
  64. package/dungeons/vertical/logistics/logistics.sql +205 -0
  65. package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
  66. package/dungeons/vertical/marketplace/marketplace.js +1438 -0
  67. package/dungeons/vertical/marketplace/marketplace.sql +148 -0
  68. package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
  69. package/dungeons/vertical/{media.js → media/media.js} +557 -58
  70. package/dungeons/vertical/media/media.sql +173 -0
  71. package/dungeons/vertical/media/media.verify.mjs +74 -0
  72. package/dungeons/vertical/real-estate/real-estate.js +1629 -0
  73. package/dungeons/vertical/real-estate/real-estate.sql +167 -0
  74. package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
  75. package/dungeons/vertical/sass/sass.js +1733 -0
  76. package/dungeons/vertical/sass/sass.sql +173 -0
  77. package/dungeons/vertical/sass/sass.verify.mjs +74 -0
  78. package/dungeons/vertical/social/social.js +1456 -0
  79. package/dungeons/vertical/social/social.sql +160 -0
  80. package/dungeons/vertical/social/social.verify.mjs +76 -0
  81. package/dungeons/vertical/streaming/streaming.js +677 -0
  82. package/dungeons/vertical/streaming/streaming.sql +101 -0
  83. package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
  84. package/dungeons/vertical/support-desk/support-desk.js +741 -0
  85. package/dungeons/vertical/support-desk/support-desk.sql +137 -0
  86. package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
  87. package/dungeons/vertical/travel/travel.js +1603 -0
  88. package/dungeons/vertical/travel/travel.sql +236 -0
  89. package/dungeons/vertical/travel/travel.verify.mjs +72 -0
  90. package/index.js +2 -2
  91. package/lib/core/config-validator.js +37 -2
  92. package/lib/core/context.js +4 -2
  93. package/lib/core/dungeon-loader.js +20 -15
  94. package/lib/core/dungeon-to-json.js +22 -4
  95. package/lib/core/storage.js +3 -3
  96. package/lib/generators/funnels.js +36 -9
  97. package/lib/hook-helpers/cohort.js +53 -0
  98. package/lib/hook-helpers/index.js +8 -0
  99. package/lib/hook-helpers/mutate.js +1 -1
  100. package/lib/hook-helpers/shape.js +319 -0
  101. package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
  102. package/lib/hook-patterns/attributed-by-source.js +60 -58
  103. package/lib/hook-patterns/frequency-by-frequency.js +14 -4
  104. package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
  105. package/lib/hook-patterns/index.js +1 -1
  106. package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
  107. package/lib/orchestrators/user-loop.js +41 -0
  108. package/lib/templates/story-spec.schema.json +175 -0
  109. package/lib/utils/json-evaluator.js +1 -1
  110. package/lib/utils/utils.js +5 -4
  111. package/lib/verify/coerce.js +186 -0
  112. package/lib/verify/counting.js +270 -32
  113. package/lib/verify/emulate-breakdown.js +1285 -203
  114. package/lib/verify/first-time.js +89 -0
  115. package/lib/verify/flows.js +679 -0
  116. package/lib/verify/formula.js +259 -0
  117. package/lib/verify/funnel-engine.js +697 -113
  118. package/lib/verify/index.js +32 -4
  119. package/lib/verify/sessionize.js +275 -0
  120. package/lib/verify/story-runner.js +483 -0
  121. package/lib/verify/verify-dungeon.js +59 -37
  122. package/package.json +3 -2
  123. package/scripts/extract-dungeon-schema.mjs +2 -2
  124. package/scripts/run-many.mjs +2 -2
  125. package/scripts/verify-runner.mjs +2 -2
  126. package/scripts/verify-stories.mjs +244 -0
  127. package/types.d.ts +296 -30
  128. package/dungeons/vertical/ai-platform.js +0 -818
  129. package/dungeons/vertical/community.js +0 -966
  130. package/dungeons/vertical/crypto.js +0 -895
  131. package/dungeons/vertical/dating.js +0 -830
  132. package/dungeons/vertical/devtools.js +0 -1050
  133. package/dungeons/vertical/ecommerce.js +0 -811
  134. package/dungeons/vertical/education.js +0 -964
  135. package/dungeons/vertical/fintech.js +0 -946
  136. package/dungeons/vertical/fitness.js +0 -935
  137. package/dungeons/vertical/food-delivery.js +0 -934
  138. package/dungeons/vertical/gaming.js +0 -1120
  139. package/dungeons/vertical/healthcare.js +0 -945
  140. package/dungeons/vertical/insurance-application.js +0 -1039
  141. package/dungeons/vertical/logistics.js +0 -986
  142. package/dungeons/vertical/marketplace.js +0 -1019
  143. package/dungeons/vertical/real-estate.js +0 -872
  144. package/dungeons/vertical/sass.js +0 -995
  145. package/dungeons/vertical/social.js +0 -904
  146. package/dungeons/vertical/travel.js +0 -930
@@ -1,1039 +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
- /** @typedef {import("../../types").Dungeon} Config */
8
-
9
- // ── OVERVIEW ──
10
- /*
11
- * NAME: SafeHaven Insurance
12
- * APP: Modern insurance web application. Users browse coverage options,
13
- * request quotes, complete multi-step applications, manage policies,
14
- * file claims, and make premium payments. Spans auto / home / life /
15
- * health / renters insurance types across web, iOS, and Android.
16
- * SCALE: 15,000 users, ~1.8M events, 121 days (2026-01-01 → 2026-05-01)
17
- * CORE LOOP: account created → quote requested → application submitted → policy activated → payment made
18
- *
19
- * EVENTS (18):
20
- * page viewed (10) > application step completed (6) > quote requested (5)
21
- * > payment made (5) > quote received (4) > application started (4)
22
- * > claim status checked (4) > support ticket created (4) > coverage reviewed (4)
23
- * > document uploaded (3) > application submitted (3) > support ticket resolved (3)
24
- * > application approved (2) > policy activated (2) > claim filed (2)
25
- * > profile updated (2) > renewal completed (2) > account created (1)
26
- *
27
- * FUNNELS (5):
28
- * - Onboarding: account created → page viewed → quote requested (85%)
29
- * - Application Completion: application started → step completed → document uploaded → application submitted (60%)
30
- * - Application Approval: application submitted → application approved → policy activated (70%)
31
- * - Claims Process: claim filed → claim status checked → support ticket created (50%, A/B experiment)
32
- * - Policy Renewal: coverage reviewed → payment made → renewal completed (65%)
33
- *
34
- * USER PROPS: Platform, insurance_type, app_version, age_range, risk_profile, policy_count, lifetime_premium, preferred_contact
35
- * SUPER PROPS: Platform, insurance_type, app_version
36
- * SCD PROPS: policy_status (pending/active/lapsed/renewed, monthly fuzzy, max 8)
37
- * GROUPS: none
38
- */
39
-
40
- // ── HOOK STORIES ──
41
- /*
42
- * NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable
43
- * via raw-prop breakdowns (date, app_version, issue_category) or
44
- * behavioral cohorts.
45
- *
46
- * -------------------------------------------------------------------
47
- * 1. VERSION STAMPING (everything)
48
- * -------------------------------------------------------------------
49
- *
50
- * PATTERN: Every event gets a deterministic app_version based on its
51
- * final timestamp. All users shift simultaneously on release dates:
52
- * - Days 0-30: v2.10
53
- * - Days 30-60: v2.11
54
- * - Days 60-90: v2.12
55
- * - Last 10 days: v2.13
56
- * app_version is a real product property already in superProps.
57
- *
58
- * HOW TO FIND IT IN MIXPANEL:
59
- *
60
- * Report 1: Event Volume by App Version
61
- * - Report type: Insights
62
- * - Event: "page viewed"
63
- * - Measure: Total
64
- * - Breakdown: "app_version"
65
- * - Line chart by day
66
- * - Expected: clean cutovers between versions, no overlap
67
- *
68
- * REAL-WORLD ANALOGUE: Forced auto-update SaaS apps cut all users over
69
- * on release day, producing crisp version bands.
70
- *
71
- * -------------------------------------------------------------------
72
- * 2. SUPPORT TICKET VOLUME DROP (everything)
73
- * -------------------------------------------------------------------
74
- *
75
- * PATTERN: Pre-v2.13: each user gets 2-3 extra cloned support-ticket
76
- * created events with bug-related issue_category values (form_crash,
77
- * login_error, page_timeout, payment_failure — added to event config).
78
- * Post-v2.13: tickets progressively removed (30% day 1 → 85% day 10).
79
- * No flag — discover via issue_category breakdown over time.
80
- *
81
- * HOW TO FIND IT IN MIXPANEL:
82
- *
83
- * Report 1: Ticket Volume Over Time
84
- * - Report type: Insights
85
- * - Event: "support ticket created"
86
- * - Measure: Total
87
- * - Breakdown: "app_version"
88
- * - Line chart by day
89
- * - Expected: high volume on v2.12, sharp drop on v2.13
90
- *
91
- * Report 2: Bug Category Share Pre vs Post v2.13
92
- * - Report type: Insights
93
- * - Event: "support ticket created"
94
- * - Measure: Total
95
- * - Breakdown: "issue_category"
96
- * - Compare pre-v2.13 vs v2.13 date ranges
97
- * - Expected: form_crash / login_error / page_timeout / payment_failure
98
- * dominate pre-v2.13, vanish post-v2.13
99
- *
100
- * REAL-WORLD ANALOGUE: A UX-fix release reduces ticket volume overnight.
101
- *
102
- * -------------------------------------------------------------------
103
- * 3. APPLICATION CONVERSION BOOST (everything)
104
- * -------------------------------------------------------------------
105
- *
106
- * PATTERN: Pre-v2.13: ~95% of users have ALL their application approved +
107
- * policy activated events dropped (per-user gating). Post-v2.13: kept.
108
- * No flag — discover via funnel by app_version or volume line chart over time.
109
- *
110
- * HOW TO FIND IT IN MIXPANEL:
111
- *
112
- * Report 1: Application Funnel by Version
113
- * - Report type: Funnels
114
- * - Steps: "application submitted" -> "application approved" -> "policy activated"
115
- * - Breakdown: "app_version"
116
- * - Expected: post-v2.13 ~ 1.3x pre-v2.13 conversion rate
117
- * (~58% v2.13 vs ~44% v2.12; some dilution because users span versions)
118
- *
119
- * REAL-WORLD ANALOGUE: A buggy multi-step form fixed by release.
120
- *
121
- * -------------------------------------------------------------------
122
- * 4. APPLICATION-STEP MAGIC NUMBER (everything)
123
- * -------------------------------------------------------------------
124
- *
125
- * PATTERN: Users with 8-14 application step completed events sit in the
126
- * sweet spot — approved_premium boosted +35%. Users with 15+ steps
127
- * are over-engaged (likely fraud or signal review); 40% of their
128
- * application approved events drop. No flag.
129
- *
130
- * HOW TO FIND IT IN MIXPANEL:
131
- *
132
- * Report 1: Avg Approved Premium by Step Bucket
133
- * - Report type: Insights (with cohort)
134
- * - Cohort A: users with 8-14 "application step completed"
135
- * - Cohort B: users with 0-7
136
- * - Event: "application approved"
137
- * - Measure: Average of "approved_premium"
138
- * - Expected: A ~ 1.35x B
139
- *
140
- * Report 2: Approvals per User on Heavy Step-Completers
141
- * - Report type: Insights (with cohort)
142
- * - Cohort C: users with >= 15 "application step completed"
143
- * - Cohort A: users with 8-14
144
- * - Event: "application approved"
145
- * - Measure: Total per user
146
- * - Expected: C ~ 40% fewer approvals per user vs A
147
- *
148
- * REAL-WORLD ANALOGUE: Engaged applicants get higher premiums approved;
149
- * over-engaged ones look like fraud and get flagged.
150
- *
151
- * -------------------------------------------------------------------
152
- * 5. APPLICATION COMPLETION TIME-TO-CONVERT (everything)
153
- * -------------------------------------------------------------------
154
- *
155
- * PATTERN: In the everything hook, place each "application approved"
156
- * event at a fixed target offset from the first "application started"
157
- * event, determined by account_type (assigned via deterministic hash):
158
- * - business: ~36h target offset (~0.75x baseline)
159
- * - individual: ~48h target offset (baseline)
160
- * - family: ~63h target offset (~1.31x baseline)
161
- *
162
- * HOW TO FIND IT IN MIXPANEL:
163
- *
164
- * Report 1: Application Funnel TTC by Account Type
165
- * - Report type: Funnels
166
- * - Steps: "application started" → "application approved"
167
- * - Breakdown: account_type (from account created event)
168
- * - Measure: Median time to convert
169
- * - Expected: business < individual < family
170
- *
171
- * REAL-WORLD ANALOGUE: Business applicants have streamlined processes
172
- * and pre-filled forms; family policies require more documentation.
173
- *
174
- * -------------------------------------------------------------------
175
- * 6. CLAIMS PROCESS EXPERIMENT (funnel config — no hook code)
176
- * -------------------------------------------------------------------
177
- *
178
- * PATTERN: A/B experiment on the Claims Process funnel. The engine
179
- * fires $experiment_started events and applies conversion/TTC
180
- * multipliers automatically. "Simplified Claims" variant gets 1.3x
181
- * conversion and 0.8x time-to-convert. Experiment runs in the last
182
- * 35 days of the dataset.
183
- *
184
- * HOW TO FIND IT IN MIXPANEL:
185
- *
186
- * Report 1: Experiment Results
187
- * - Report type: Funnels
188
- * - Steps: "claim filed" -> "claim status checked" -> "support ticket created"
189
- * - Breakdown: "Variant name"
190
- * - Date range: last 35 days of dataset
191
- * - Expected: "Simplified Claims" has ~1.3x conversion vs "Control"
192
- *
193
- * Report 2: Experiment Started Events
194
- * - Report type: Insights
195
- * - Event: "$experiment_started"
196
- * - Breakdown: "Variant name"
197
- * - Expected: roughly equal distribution between Control and Simplified Claims
198
- *
199
- * REAL-WORLD ANALOGUE: A/B testing a simplified claims flow to reduce
200
- * friction and improve completion rates.
201
- *
202
- * -------------------------------------------------------------------
203
- * 7. RISK PROFILE AFFECTS APPROVAL (funnel-pre)
204
- * -------------------------------------------------------------------
205
- *
206
- * PATTERN: In the funnel-pre hook, low-risk users get 1.8x approval
207
- * funnel conversion (capped at 95%), high-risk users get 0.3x.
208
- * Medium-risk users are unchanged.
209
- *
210
- * HOW TO FIND IT IN MIXPANEL:
211
- *
212
- * Report 1: Application Approval Funnel by Risk Profile
213
- * - Report type: Funnels
214
- * - Steps: "application submitted" -> "application approved" -> "policy activated"
215
- * - Breakdown: "risk_profile" (user property)
216
- * - Expected: low >> medium >> high conversion rates
217
- *
218
- * REAL-WORLD ANALOGUE: Underwriting engines auto-approve low-risk
219
- * applicants and require manual review for high-risk ones.
220
- *
221
- * -------------------------------------------------------------------
222
- * 8. DOCUMENT UPLOAD RETENTION (everything — retention magic number)
223
- * -------------------------------------------------------------------
224
- *
225
- * PATTERN: Users who upload 3+ documents in their first 14 days
226
- * retain normally. Users with fewer uploads lose 75% of events after
227
- * day 30. Only affects born-in-dataset users.
228
- *
229
- * HOW TO FIND IT IN MIXPANEL:
230
- *
231
- * Report 1: Retention by Document Upload Cohort
232
- * - Report type: Retention
233
- * - Starting event: "account created"
234
- * - Return event: any event
235
- * - Cohort A: users with >= 3 "document uploaded" in first 14 days
236
- * - Cohort B: users with < 3 "document uploaded" in first 14 days
237
- * - Expected: Cohort A retains well past day 30; Cohort B drops off sharply
238
- *
239
- * Report 2: Post-Day-30 Event Volume
240
- * - Report type: Insights
241
- * - Event: all events
242
- * - Compare doc-uploader cohort vs non-uploader
243
- * - Expected: non-uploaders have ~75% fewer events after day 30
244
- *
245
- * REAL-WORLD ANALOGUE: Users who complete onboarding paperwork early
246
- * are invested in the product and retain; those who don't churn.
247
- *
248
- * -------------------------------------------------------------------
249
- * 9. END-OF-QUARTER RENEWAL SPIKE (everything — temporal)
250
- * -------------------------------------------------------------------
251
- *
252
- * PATTERN: Days 85-95 of the dataset get 3x renewal completed events
253
- * and 2x coverage reviewed events via cloning. Simulates end-of-quarter
254
- * policy renewal batch processing.
255
- *
256
- * HOW TO FIND IT IN MIXPANEL:
257
- *
258
- * Report 1: Renewal Volume Over Time
259
- * - Report type: Insights
260
- * - Event: "renewal completed"
261
- * - Measure: Total
262
- * - Line chart by day
263
- * - Expected: visible spike at days 85-95, ~3x baseline volume
264
- *
265
- * Report 2: Coverage Review Spike
266
- * - Report type: Insights
267
- * - Event: "coverage reviewed"
268
- * - Line chart by day
269
- * - Expected: ~2x volume during days 85-95
270
- *
271
- * REAL-WORLD ANALOGUE: Insurance companies batch-process renewals at
272
- * quarter-end, producing predictable volume spikes.
273
- *
274
- * -------------------------------------------------------------------
275
- * 10. CLAIM-TO-PREMIUM INCREASE (event — closure Map)
276
- * -------------------------------------------------------------------
277
- *
278
- * PATTERN: After a user files a claim, their next "payment made" event
279
- * gets premium_amount doubled (2.0x). Uses a module-level Map to
280
- * track state across events. The Map entry is consumed (deleted) on
281
- * the first payment after the claim, so the effect is one-shot.
282
- *
283
- * HOW TO FIND IT IN MIXPANEL:
284
- *
285
- * Report 1: Average Premium After Claim
286
- * - Report type: Insights
287
- * - Event: "payment made"
288
- * - Measure: Average of "premium_amount"
289
- * - Cohort A: users who did "claim filed" before
290
- * - Cohort B: users who never filed a claim
291
- * - Expected: Cohort A premium_amount ~ 2.0x Cohort B
292
- *
293
- * REAL-WORLD ANALOGUE: Insurance premiums increase after filing a claim.
294
- *
295
- * ===================================================================
296
- * EXPECTED METRICS SUMMARY
297
- * ===================================================================
298
- *
299
- * Hook | Metric | Baseline | Effect | Ratio
300
- * ------------------------|-------------------------|----------|---------|------
301
- * Version Stamping | Events per version | n/a | clean | bands
302
- * Support Ticket Volume | tickets v2.12 -> v2.13 | 1x | ~ 0.3x | -70%
303
- * Application Conversion | approval rate pre/post | 1x | ~ 1.3x | step-up
304
- * Step-Count Magic Number | sweet approved_premium | 1x | 1.35x | 1.35x
305
- * Step-Count Magic Number | over approvals/user | 1x | 0.6x | -40%
306
- * Application TTC | business vs individual | 1x | 0.74x | faster
307
- * Application TTC | family vs individual | 1x | 1.3x | slower
308
- * Claims Experiment | simplified vs control | 1x | 1.3x | +30%
309
- * Risk Profile Approval | low vs medium conv rate | 1x | 1.8x | +80%
310
- * Risk Profile Approval | high vs medium conv rate| 1x | 0.3x | -70%
311
- * Document Upload Ret. | post-d30 non-uploaders | 1x | 0.25x | -75%
312
- * Renewal Spike | renewals d85-95 vs base | 1x | 3x | +200%
313
- * Renewal Spike | reviews d85-95 vs base | 1x | 2x | +100%
314
- * Claim-to-Premium | premium after claim | 1x | 2.0x | +100%
315
- */
316
-
317
- // ── SCALE ──
318
- const SEED = "dm4-insurance";
319
- const NUM_USERS = 15_000;
320
- const DATASET_START = "2026-01-01T00:00:00Z";
321
- const DATASET_END = "2026-05-01T23:59:59Z";
322
- const EVENTS_PER_DAY = 1.2;
323
- const token = process.env.MP_TOKEN || "your-mixpanel-token";
324
-
325
- const chance = u.initChance(SEED);
326
-
327
- // ── KNOBS (tweak these to reshape stories) ──
328
- // H1: Version stamping cutoffs (days from datasetStart / before datasetEnd)
329
- const V211_DAY = 30;
330
- const V212_DAY = 60;
331
- const V213_DAYS_BEFORE_END = 10;
332
-
333
- // H2: Support ticket volume
334
- const TICKET_INJECT_MIN = 2;
335
- const TICKET_INJECT_MAX = 3;
336
- const TICKET_REMOVAL_BASE_PCT = 30;
337
- const TICKET_REMOVAL_PER_DAY_PCT = 5.5;
338
- const TICKET_REMOVAL_CAP_PCT = 85;
339
-
340
- // H3: Application conversion boost
341
- const PRE_V213_APPROVAL_DROP_LIKELIHOOD = 95;
342
-
343
- // H4: Application-step magic number
344
- const STEP_SWEET_MIN = 8;
345
- const STEP_SWEET_MAX = 14;
346
- const STEP_OVER_THRESHOLD = 15;
347
- const STEP_PREMIUM_BOOST = 1.35;
348
- const STEP_OVER_DROP_LIKELIHOOD = 40;
349
-
350
- // H5: Application TTC target offsets (hours from first application started)
351
- const TTC_BUSINESS_HOURS = 36;
352
- const TTC_INDIVIDUAL_HOURS = 48;
353
- const TTC_FAMILY_HOURS = 63;
354
-
355
- // H7: Risk profile approval multipliers
356
- const RISK_LOW_CONV_MULT = 1.8;
357
- const RISK_HIGH_CONV_MULT = 0.3;
358
- const RISK_CONV_CAP = 95;
359
-
360
- // H8: Document upload retention
361
- const DOC_RETENTION_MIN = 3;
362
- const DOC_RETENTION_WINDOW_DAYS = 14;
363
- const DOC_RETENTION_CUTOFF_DAYS = 30;
364
- const DOC_RETENTION_DROP_LIKELIHOOD = 75;
365
-
366
- // H9: End-of-quarter renewal spike (days from datasetStart)
367
- const RENEWAL_SPIKE_START_DAY = 85;
368
- const RENEWAL_SPIKE_END_DAY = 95;
369
- const RENEWAL_CLONE_COUNT = 2; // 2 extra clones per renewal → 3x volume
370
- const COVERAGE_CLONE_COUNT = 1; // 1 extra clone per coverage review → 2x volume
371
-
372
- // H10: Claim-to-premium increase
373
- const POST_CLAIM_PREMIUM_MULT = 2.0;
374
-
375
- // ── HOOK STATE ──
376
- // H10 closure: tracks users who filed a claim (one-shot consumption)
377
- const claimFilers = new Map();
378
-
379
- // ── HELPER FUNCTIONS ──
380
- function handleFunnelPreHooks(record, meta) {
381
- // H7: RISK PROFILE AFFECTS APPROVAL — low-risk 1.8x, high-risk 0.3x conversion
382
- const isApprovalFunnel = meta.funnel?.sequence?.includes("application approved");
383
- if (isApprovalFunnel) {
384
- const risk = meta.profile?.risk_profile;
385
- if (risk === "low") record.conversionRate = Math.min(RISK_CONV_CAP, Math.round(record.conversionRate * RISK_LOW_CONV_MULT));
386
- else if (risk === "high") record.conversionRate = Math.round(record.conversionRate * RISK_HIGH_CONV_MULT);
387
- }
388
- return record;
389
- }
390
-
391
- function handleEventHooks(record) {
392
- // H10: CLAIM-TO-PREMIUM INCREASE — premium doubled on first payment after claim
393
- if (record.event === "claim filed") {
394
- claimFilers.set(record.user_id, true);
395
- }
396
- if (record.event === "payment made" && claimFilers.has(record.user_id)) {
397
- record.premium_amount = Math.round((record.premium_amount || 500) * POST_CLAIM_PREMIUM_MULT);
398
- claimFilers.delete(record.user_id);
399
- }
400
- return record;
401
- }
402
-
403
- function handleEverythingHooks(record, meta) {
404
- const datasetStart = dayjs.unix(meta.datasetStart);
405
- const datasetEnd = dayjs.unix(meta.datasetEnd);
406
- const V211_DATE = datasetStart.add(V211_DAY, "days");
407
- const V212_DATE = datasetStart.add(V212_DAY, "days");
408
- const V213_DATE = datasetEnd.subtract(V213_DAYS_BEFORE_END, "days");
409
- const userEvents = record;
410
- if (userEvents.length === 0) return record;
411
-
412
- // Stamp superProps from profile for consistency
413
- const profile = meta.profile;
414
- userEvents.forEach(e => {
415
- e.Platform = profile.Platform;
416
- e.insurance_type = profile.insurance_type;
417
- e.app_version = profile.app_version;
418
- });
419
-
420
- // Find a user_id from any existing event
421
- const userId =
422
- userEvents.find((e) => e.user_id)?.user_id ||
423
- userEvents[0]?.device_id;
424
-
425
- // ─── Hook #5: APPLICATION COMPLETION TIME-TO-CONVERT ───
426
- // Runs FIRST: adjusts timestamps before version stamping.
427
- // Business accounts complete the application funnel faster (0.74x),
428
- // family accounts slower (1.3x). Individual stays at baseline.
429
- // Assigns account_type deterministically per user via djb2 hash
430
- // (independent of engine RNG). Places each "application approved"
431
- // event at a fixed offset from the first "application started".
432
- {
433
- // djb2 hash of userId → deterministic cohort
434
- let h = 5381;
435
- for (let i = 0; i < userId.length; i++) {
436
- h = ((h << 5) + h + userId.charCodeAt(i)) | 0;
437
- }
438
- const acctTypes = ["individual", "family", "business"];
439
- const userAccountType = acctTypes[((h % 3) + 3) % 3];
440
- // Stamp account created events with the deterministic type
441
- for (const evt of userEvents) {
442
- if (evt.event === "account created") {
443
- evt.account_type = userAccountType;
444
- }
445
- }
446
- userEvents.sort((a, b) => new Date(a.time) - new Date(b.time));
447
- // Collect all "application started" times
448
- const startedTimes = [];
449
- for (const evt of userEvents) {
450
- if (evt.event === "application started") {
451
- startedTimes.push(dayjs(evt.time).valueOf());
452
- }
453
- }
454
- if (startedTimes.length > 0) {
455
- const firstStartTime = startedTimes[0];
456
- // Target hours: biz=36, indiv=48, family=63
457
- // Ratios: biz/indiv≈0.75, family/indiv≈1.31
458
- const targetHours = (
459
- userAccountType === "business" ? TTC_BUSINESS_HOURS :
460
- userAccountType === "family" ? TTC_FAMILY_HOURS :
461
- TTC_INDIVIDUAL_HOURS
462
- );
463
- const targetMs = targetHours * 3600000;
464
- for (const evt of userEvents) {
465
- if (evt.event !== "application approved") continue;
466
- const evtTime = dayjs(evt.time).valueOf();
467
- const origGap = evtTime - firstStartTime;
468
- // Small jitter from original gap (mod 4h) for variance
469
- const jitter = origGap > 0 ? (origGap % (4 * 3600000)) : 0;
470
- evt.time = dayjs(firstStartTime + targetMs + jitter).toISOString();
471
- }
472
- }
473
- }
474
-
475
- // ─── Hook #2: SUPPORT TICKET VOLUME ───
476
- // PRE-V2.13: Inject 2-3 extra support tickets with bug-related categories
477
- const preV213Tickets = userEvents.filter(
478
- (e) =>
479
- e.event === "support ticket created" &&
480
- dayjs(e.time).isBefore(V213_DATE)
481
- );
482
-
483
- if (preV213Tickets.length > 0) {
484
- const extraCount = chance.integer({ min: TICKET_INJECT_MIN, max: TICKET_INJECT_MAX });
485
- const bugCategories = [
486
- "form_crash",
487
- "login_error",
488
- "page_timeout",
489
- "payment_failure",
490
- ];
491
-
492
- for (let i = 0; i < extraCount; i++) {
493
- // Pick a random pre-v2.13 ticket to base timing on
494
- const sourceTicket = chance.pickone(preV213Tickets);
495
- const sourceTime = dayjs(sourceTicket.time);
496
- // Offset by a few hours to days
497
- const offsetHours = chance.integer({ min: 1, max: 72 });
498
- let newTime = sourceTime.add(offsetHours, "hours");
499
- // Ensure it stays before v2.13
500
- if (newTime.isAfter(V213_DATE)) {
501
- newTime = V213_DATE.subtract(
502
- chance.integer({ min: 1, max: 48 }),
503
- "hours"
504
- );
505
- }
506
-
507
- // Compute app_version for injected event
508
- let injectedVersion;
509
- if (newTime.isBefore(V211_DATE)) {
510
- injectedVersion = "2.10";
511
- } else if (newTime.isBefore(V212_DATE)) {
512
- injectedVersion = "2.11";
513
- } else {
514
- injectedVersion = "2.12"; // always pre-v2.13
515
- }
516
-
517
- userEvents.push({
518
- ...sourceTicket,
519
- time: newTime.toISOString(),
520
- user_id: userId,
521
- app_version: injectedVersion,
522
- issue_category: chance.pickone(bugCategories),
523
- priority: chance.pickone(["medium", "high", "high"]),
524
- channel: chance.pickone([
525
- "chat",
526
- "phone",
527
- "email",
528
- "web_form",
529
- ]),
530
- });
531
- }
532
- }
533
-
534
- // POST-V2.13: Remove support tickets with increasing probability
535
- // Day 1 after release: ~30% removal
536
- // Day 5: ~60% removal
537
- // Day 10: ~85% removal
538
- for (let i = userEvents.length - 1; i >= 0; i--) {
539
- const evt = userEvents[i];
540
- if (evt.event === "support ticket created") {
541
- const evtTime = dayjs(evt.time);
542
- if (evtTime.isAfter(V213_DATE)) {
543
- const daysSinceRelease = evtTime.diff(
544
- V213_DATE,
545
- "days",
546
- true
547
- );
548
- // Linear ramp: 30% base + 5.5% per day → ~85% at day 10
549
- const removalLikelihood = Math.min(
550
- TICKET_REMOVAL_CAP_PCT,
551
- TICKET_REMOVAL_BASE_PCT + daysSinceRelease * TICKET_REMOVAL_PER_DAY_PCT
552
- );
553
- if (chance.bool({ likelihood: removalLikelihood })) {
554
- userEvents.splice(i, 1);
555
- }
556
- }
557
- }
558
- }
559
-
560
- // ─── Hook #3: APPLICATION CONVERSION BOOST ───
561
- // PRE-V2.13: Remove ALL application approved + policy activated events
562
- // for ~95% of users (per-user gating, not per-event). This produces
563
- // visible funnel completion gap pre-v2.13 vs post-v2.13.
564
- if (chance.bool({ likelihood: PRE_V213_APPROVAL_DROP_LIKELIHOOD })) {
565
- for (let i = userEvents.length - 1; i >= 0; i--) {
566
- const evt = userEvents[i];
567
- if (
568
- (evt.event === "application approved" ||
569
- evt.event === "policy activated") &&
570
- dayjs(evt.time).isBefore(V213_DATE)
571
- ) {
572
- userEvents.splice(i, 1);
573
- }
574
- }
575
- }
576
-
577
- // Hook 4: APPLICATION-STEP MAGIC NUMBER (no flags)
578
- // Sweet 8-14 application step completed → +35% on approved_premium
579
- // for application approved events. Over 15+ → drop 40% of
580
- // application approved events (fraud filter triggers).
581
- const stepCount = userEvents.filter(e => e.event === "application step completed").length;
582
- if (stepCount >= STEP_SWEET_MIN && stepCount <= STEP_SWEET_MAX) {
583
- userEvents.forEach(e => {
584
- if (e.event === "application approved" && typeof e.approved_premium === "number") {
585
- e.approved_premium = Math.round(e.approved_premium * STEP_PREMIUM_BOOST);
586
- }
587
- });
588
- } else if (stepCount >= STEP_OVER_THRESHOLD) {
589
- for (let i = userEvents.length - 1; i >= 0; i--) {
590
- if (userEvents[i].event === "application approved" && chance.bool({ likelihood: STEP_OVER_DROP_LIKELIHOOD })) {
591
- userEvents.splice(i, 1);
592
- }
593
- }
594
- }
595
-
596
- // ─── Hook #8: DOCUMENT UPLOAD RETENTION ───
597
- // Users with 3+ "document uploaded" in first 14 days retain;
598
- // others lose 75% of events post-day-30.
599
- if (meta.userIsBornInDataset) {
600
- const firstT = userEvents[0]?.time;
601
- if (firstT) {
602
- const window14 = dayjs(firstT).add(DOC_RETENTION_WINDOW_DAYS, "days").toISOString();
603
- const docUploads = userEvents.filter(
604
- (e) => e.event === "document uploaded" && e.time <= window14
605
- ).length;
606
- if (docUploads < DOC_RETENTION_MIN) {
607
- const cutoff = dayjs(firstT).add(DOC_RETENTION_CUTOFF_DAYS, "days");
608
- for (let i = userEvents.length - 1; i >= 0; i--) {
609
- if (
610
- dayjs(userEvents[i].time).isAfter(cutoff) &&
611
- chance.bool({ likelihood: DOC_RETENTION_DROP_LIKELIHOOD })
612
- ) {
613
- userEvents.splice(i, 1);
614
- }
615
- }
616
- }
617
- }
618
- }
619
-
620
- // ─── Hook #9: END-OF-QUARTER RENEWAL SPIKE ───
621
- // Days 85-95 get 3x renewal clones + 2x coverage reviewed clones
622
- {
623
- const spikeStart = datasetStart.add(RENEWAL_SPIKE_START_DAY, "days");
624
- const spikeEnd = datasetStart.add(RENEWAL_SPIKE_END_DAY, "days");
625
- const clones = [];
626
- userEvents.forEach((e) => {
627
- const t = dayjs(e.time);
628
- if (t.isAfter(spikeStart) && t.isBefore(spikeEnd)) {
629
- if (e.event === "renewal completed") {
630
- for (let c = 0; c < RENEWAL_CLONE_COUNT; c++) {
631
- clones.push({
632
- ...e,
633
- time: t
634
- .add(
635
- chance.integer({ min: 5, max: 240 }),
636
- "minutes"
637
- )
638
- .toISOString(),
639
- insert_id: chance.guid(),
640
- });
641
- }
642
- }
643
- if (e.event === "coverage reviewed") {
644
- for (let c = 0; c < COVERAGE_CLONE_COUNT; c++) {
645
- clones.push({
646
- ...e,
647
- time: t
648
- .add(
649
- chance.integer({ min: 5, max: 120 }),
650
- "minutes"
651
- )
652
- .toISOString(),
653
- insert_id: chance.guid(),
654
- });
655
- }
656
- }
657
- }
658
- });
659
- if (clones.length) userEvents.push(...clones);
660
- }
661
-
662
- // ─── Hook #1: VERSION STAMPING ───
663
- // Runs LAST: stamps app_version based on FINAL timestamps
664
- // (after Hook #5 TTC scaling has adjusted event times).
665
- // v2.10 (days 0-30) → v2.11 (30-60) → v2.12 (60-90) → v2.13 (last 10 days)
666
- for (const evt of userEvents) {
667
- const eventTime = dayjs(evt.time);
668
- if (eventTime.isBefore(V211_DATE)) {
669
- evt.app_version = "2.10";
670
- } else if (eventTime.isBefore(V212_DATE)) {
671
- evt.app_version = "2.11";
672
- } else if (eventTime.isBefore(V213_DATE)) {
673
- evt.app_version = "2.12";
674
- } else {
675
- evt.app_version = "2.13";
676
- }
677
- }
678
-
679
- // Sort events by time after injection/removal/shifting
680
- userEvents.sort(
681
- (a, b) => new Date(a.time) - new Date(b.time)
682
- );
683
-
684
- return record;
685
- }
686
-
687
- // ── CONFIG ──
688
- /** @type {Config} */
689
- const config = {
690
- version: 2,
691
- seed: SEED,
692
- datasetStart: DATASET_START,
693
- datasetEnd: DATASET_END,
694
- avgEventsPerUserPerDay: EVENTS_PER_DAY,
695
- numUsers: NUM_USERS,
696
- format: "json",
697
- gzip: true,
698
- credentials: {
699
- token,
700
- },
701
- switches: {
702
- hasSessionIds: true,
703
- alsoInferFunnels: false,
704
- hasLocation: true,
705
- hasAndroidDevices: true,
706
- hasIOSDevices: true,
707
- hasDesktopDevices: true,
708
- hasBrowser: false,
709
- hasCampaigns: false,
710
- isAnonymous: false,
711
- hasAdSpend: false,
712
- hasAvatar: true,
713
- },
714
- identity: {
715
- avgDevicePerUser: 2,
716
- },
717
- concurrency: 1,
718
- writeToDisk: false,
719
-
720
- scdProps: {
721
- policy_status: {
722
- values: ["pending", "active", "lapsed", "renewed"],
723
- frequency: "month",
724
- timing: "fuzzy",
725
- max: 8
726
- }
727
- },
728
- mirrorProps: {},
729
- lookupTables: [],
730
-
731
- events: [
732
- {
733
- event: "account created",
734
- weight: 1,
735
- isFirstEvent: true,
736
- isAuthEvent: true,
737
- properties: {
738
- signup_source: ["web", "mobile", "agent_referral", "partner"],
739
- account_type: ["individual", "family", "business"],
740
- },
741
- },
742
- {
743
- event: "page viewed",
744
- weight: 10,
745
- properties: {
746
- page_name: [
747
- "home",
748
- "quotes",
749
- "coverage_options",
750
- "claims",
751
- "faq",
752
- "profile",
753
- "payment",
754
- "documents",
755
- ],
756
- referrer: ["direct", "google", "email", "social_media"],
757
- },
758
- },
759
- {
760
- event: "quote requested",
761
- weight: 5,
762
- properties: {
763
- coverage_level: ["basic", "standard", "premium"],
764
- deductible: u.weighNumRange(250, 5000, 0.5, 1000),
765
- },
766
- },
767
- {
768
- event: "quote received",
769
- weight: 4,
770
- properties: {
771
- monthly_premium: u.weighNumRange(30, 800, 0.3, 150),
772
- coverage_amount: u.weighNumRange(10000, 500000, 0.3, 100000),
773
- quote_comparison_count: u.weighNumRange(1, 5, 0.5, 2),
774
- },
775
- },
776
- {
777
- event: "application started",
778
- weight: 4,
779
- isStrictEvent: false,
780
- properties: {
781
- coverage_level: ["basic", "standard", "premium"],
782
- estimated_premium: u.weighNumRange(30, 800, 0.3, 150),
783
- },
784
- },
785
- {
786
- event: "application step completed",
787
- weight: 6,
788
- isStrictEvent: false,
789
- properties: {
790
- step_name: [
791
- "personal_info",
792
- "coverage_selection",
793
- "medical_history",
794
- "vehicle_info",
795
- "beneficiary",
796
- "review",
797
- ],
798
- step_number: u.weighNumRange(1, 6),
799
- time_on_step_sec: u.weighNumRange(15, 600, 0.3, 90),
800
- },
801
- },
802
- {
803
- event: "document uploaded",
804
- weight: 3,
805
- isStrictEvent: false,
806
- properties: {
807
- document_type: [
808
- "drivers_license",
809
- "proof_of_address",
810
- "vehicle_registration",
811
- "medical_records",
812
- "property_photos",
813
- ],
814
- file_size_kb: u.weighNumRange(50, 5000, 0.5, 500),
815
- },
816
- },
817
- {
818
- event: "application submitted",
819
- weight: 3,
820
- properties: {
821
- documents_attached: u.weighNumRange(1, 5, 0.5, 2),
822
- application_time_min: u.weighNumRange(5, 60, 0.3, 15),
823
- },
824
- },
825
- {
826
- event: "application approved",
827
- weight: 2,
828
- isStrictEvent: false,
829
- properties: {
830
- approved_premium: u.weighNumRange(30, 800, 0.3, 150),
831
- approval_time_hours: u.weighNumRange(1, 72, 0.5, 24),
832
- },
833
- },
834
- {
835
- event: "policy activated",
836
- weight: 2,
837
- isStrictEvent: false,
838
- properties: {
839
- policy_term_months: [6, 12, 12, 12, 24],
840
- effective_date_offset_days: u.weighNumRange(0, 30, 0.5, 7),
841
- },
842
- },
843
- {
844
- event: "claim filed",
845
- weight: 2,
846
- isStrictEvent: false,
847
- properties: {
848
- claim_type: [
849
- "collision",
850
- "theft",
851
- "water_damage",
852
- "fire",
853
- "medical",
854
- "liability",
855
- ],
856
- estimated_amount: u.weighNumRange(100, 50000, 0.3, 3000),
857
- },
858
- },
859
- {
860
- event: "claim status checked",
861
- weight: 4,
862
- properties: {
863
- claim_status: [
864
- "submitted",
865
- "under_review",
866
- "approved",
867
- "denied",
868
- "payment_pending",
869
- ],
870
- days_since_filed: u.weighNumRange(1, 60, 0.5, 10),
871
- },
872
- },
873
- {
874
- event: "payment made",
875
- weight: 5,
876
- isStrictEvent: false,
877
- properties: {
878
- payment_method: ["credit_card", "bank_transfer", "auto_pay", "check"],
879
- amount: u.weighNumRange(30, 800, 0.3, 150),
880
- premium_amount: u.weighNumRange(50, 600, 0.3, 150),
881
- payment_status: ["success", "success", "success", "success", "failed"],
882
- },
883
- },
884
- {
885
- event: "support ticket created",
886
- weight: 4,
887
- isStrictEvent: false,
888
- properties: {
889
- issue_category: [
890
- "billing",
891
- "claims",
892
- "coverage",
893
- "technical",
894
- "policy_change",
895
- "form_crash",
896
- "login_error",
897
- "page_timeout",
898
- "payment_failure",
899
- ],
900
- priority: ["low", "medium", "medium", "high"],
901
- channel: ["chat", "phone", "email", "web_form"],
902
- },
903
- },
904
- {
905
- event: "support ticket resolved",
906
- weight: 3,
907
- properties: {
908
- resolution_type: [
909
- "self_service",
910
- "agent_assisted",
911
- "escalated",
912
- "auto_resolved",
913
- ],
914
- satisfaction_score: u.weighNumRange(1, 5, 1, 4),
915
- resolution_time_hours: u.weighNumRange(0.5, 72, 0.3, 8),
916
- },
917
- },
918
- {
919
- event: "coverage reviewed",
920
- weight: 4,
921
- isStrictEvent: false,
922
- properties: {
923
- current_premium: u.weighNumRange(30, 800, 0.3, 150),
924
- coverage_adequate: [true, true, true, false],
925
- },
926
- },
927
- {
928
- event: "profile updated",
929
- weight: 2,
930
- properties: {
931
- field_updated: [
932
- "address",
933
- "phone",
934
- "email",
935
- "beneficiary",
936
- "payment_method",
937
- "vehicle_info",
938
- ],
939
- },
940
- },
941
- {
942
- event: "renewal completed",
943
- weight: 2,
944
- isStrictEvent: false,
945
- properties: {
946
- renewal_premium: u.weighNumRange(30, 800, 0.3, 150),
947
- premium_change_pct: u.weighNumRange(-15, 20, 1, 3),
948
- auto_renewed: [true, true, false],
949
- },
950
- },
951
- ],
952
-
953
- funnels: [
954
- {
955
- name: "Onboarding",
956
- sequence: ["account created", "page viewed", "quote requested"],
957
- isFirstFunnel: true,
958
- conversionRate: 85,
959
- timeToConvert: 0.5,
960
- },
961
- {
962
- name: "Application Completion",
963
- sequence: [
964
- "application started",
965
- "application step completed",
966
- "document uploaded",
967
- "application submitted",
968
- ],
969
- conversionRate: 60,
970
- timeToConvert: 48,
971
- weight: 4,
972
- order: "sequential",
973
- },
974
- {
975
- name: "Application Approval",
976
- sequence: [
977
- "application submitted",
978
- "application approved",
979
- "policy activated",
980
- ],
981
- conversionRate: 70,
982
- timeToConvert: 72,
983
- weight: 3,
984
- order: "sequential",
985
- },
986
- {
987
- name: "Claims Process",
988
- sequence: [
989
- "claim filed",
990
- "claim status checked",
991
- "support ticket created",
992
- ],
993
- conversionRate: 50,
994
- timeToConvert: 24,
995
- weight: 2,
996
- experiment: {
997
- name: "Simplified Claims Flow",
998
- variants: [
999
- { name: "Control" },
1000
- { name: "Simplified Claims", conversionMultiplier: 1.3, ttcMultiplier: 0.8 },
1001
- ],
1002
- startDaysBeforeEnd: 35,
1003
- },
1004
- },
1005
- {
1006
- name: "Policy Renewal",
1007
- sequence: ["coverage reviewed", "payment made", "renewal completed"],
1008
- conversionRate: 65,
1009
- timeToConvert: 72,
1010
- weight: 3,
1011
- },
1012
- ],
1013
-
1014
- superProps: {
1015
- Platform: ["web", "ios", "android"],
1016
- insurance_type: ["auto", "home", "life", "health", "renters"],
1017
- app_version: ["2.10"],
1018
- },
1019
-
1020
- userProps: {
1021
- Platform: ["web", "ios", "android"],
1022
- insurance_type: ["auto", "home", "life", "health", "renters"],
1023
- app_version: ["2.10"],
1024
- age_range: ["18-25", "26-35", "36-45", "46-55", "56-65", "65+"],
1025
- risk_profile: ["low", "medium", "high"],
1026
- policy_count: u.weighNumRange(0, 5, 0.5, 1),
1027
- lifetime_premium: u.weighNumRange(0, 50000, 0.3, 5000),
1028
- preferred_contact: ["email", "phone", "app_notification"],
1029
- },
1030
-
1031
- hook(record, type, meta) {
1032
- if (type === "funnel-pre") return handleFunnelPreHooks(record, meta);
1033
- if (type === "event") return handleEventHooks(record);
1034
- if (type === "everything") return handleEverythingHooks(record, meta);
1035
- return record;
1036
- },
1037
- };
1038
-
1039
- export default config;