@ak--47/dungeon-master 1.5.4 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) 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 +1 -1
  4. package/.claude/skills/create-project/provision.mjs +57 -10
  5. package/.claude/skills/powertools/SKILL.md +75 -0
  6. package/.claude/skills/powertools/pt.mjs +91 -0
  7. package/.claude/skills/powertools/snapshot-project.mjs +124 -0
  8. package/.claude/skills/verify-dungeon/SKILL.md +56 -25
  9. package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
  10. package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
  11. package/.claude/skills/write-hooks/SKILL.md +156 -18
  12. package/CHANGELOG.md +224 -0
  13. package/HOOKS.md +566 -112
  14. package/README.md +30 -9
  15. package/docs/guides/1.3.0-upgrade-guide.md +262 -0
  16. package/docs/guides/1.3.1-upgrade-guide.md +328 -0
  17. package/docs/guides/1.4.0-upgrade-guide.md +130 -0
  18. package/docs/guides/1.4.1-upgrade-guide.md +125 -0
  19. package/docs/guides/1.4.3-upgrade-guide.md +200 -0
  20. package/docs/guides/1.4.4-upgrade-guide.md +95 -0
  21. package/docs/guides/1.5.0-upgrade-guide.md +426 -0
  22. package/docs/guides/1.5.1-upgrade-guide.md +178 -0
  23. package/docs/guides/1.5.3-upgrade-guide.md +117 -0
  24. package/docs/guides/1.6.0-upgrade-guide.md +204 -0
  25. package/dungeons/technical/stories-verify.js +152 -0
  26. package/dungeons/vertical/README.md +154 -0
  27. package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
  28. package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
  29. package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
  30. package/dungeons/vertical/community/community.js +1589 -0
  31. package/dungeons/vertical/community/community.sql +216 -0
  32. package/dungeons/vertical/community/community.verify.mjs +72 -0
  33. package/dungeons/vertical/crypto/crypto.js +1483 -0
  34. package/dungeons/vertical/crypto/crypto.sql +194 -0
  35. package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
  36. package/dungeons/vertical/dating/dating.js +1540 -0
  37. package/dungeons/vertical/dating/dating.sql +288 -0
  38. package/dungeons/vertical/dating/dating.verify.mjs +72 -0
  39. package/dungeons/vertical/devtools/devtools.js +1524 -0
  40. package/dungeons/vertical/devtools/devtools.sql +161 -0
  41. package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
  42. package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
  43. package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
  44. package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
  45. package/dungeons/vertical/education/education.js +1763 -0
  46. package/dungeons/vertical/education/education.sql +243 -0
  47. package/dungeons/vertical/education/education.verify.mjs +75 -0
  48. package/dungeons/vertical/fintech/fintech.js +1410 -0
  49. package/dungeons/vertical/fintech/fintech.sql +175 -0
  50. package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
  51. package/dungeons/vertical/fitness/fitness.js +1495 -0
  52. package/dungeons/vertical/fitness/fitness.sql +206 -0
  53. package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
  54. package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
  55. package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
  56. package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
  57. package/dungeons/vertical/gaming/gaming.js +1741 -0
  58. package/dungeons/vertical/gaming/gaming.sql +249 -0
  59. package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
  60. package/dungeons/vertical/healthcare/healthcare.js +1488 -0
  61. package/dungeons/vertical/healthcare/healthcare.sql +193 -0
  62. package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
  63. package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
  64. package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
  65. package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
  66. package/dungeons/vertical/logistics/logistics.js +1541 -0
  67. package/dungeons/vertical/logistics/logistics.sql +205 -0
  68. package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
  69. package/dungeons/vertical/marketplace/marketplace.js +1438 -0
  70. package/dungeons/vertical/marketplace/marketplace.sql +148 -0
  71. package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
  72. package/dungeons/vertical/{media.js → media/media.js} +557 -58
  73. package/dungeons/vertical/media/media.sql +173 -0
  74. package/dungeons/vertical/media/media.verify.mjs +74 -0
  75. package/dungeons/vertical/real-estate/real-estate.js +1629 -0
  76. package/dungeons/vertical/real-estate/real-estate.sql +167 -0
  77. package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
  78. package/dungeons/vertical/sass/sass.js +1733 -0
  79. package/dungeons/vertical/sass/sass.sql +173 -0
  80. package/dungeons/vertical/sass/sass.verify.mjs +74 -0
  81. package/dungeons/vertical/social/social.js +1456 -0
  82. package/dungeons/vertical/social/social.sql +160 -0
  83. package/dungeons/vertical/social/social.verify.mjs +76 -0
  84. package/dungeons/vertical/streaming/streaming.js +677 -0
  85. package/dungeons/vertical/streaming/streaming.sql +101 -0
  86. package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
  87. package/dungeons/vertical/support-desk/support-desk.js +741 -0
  88. package/dungeons/vertical/support-desk/support-desk.sql +137 -0
  89. package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
  90. package/dungeons/vertical/travel/travel.js +1603 -0
  91. package/dungeons/vertical/travel/travel.sql +236 -0
  92. package/dungeons/vertical/travel/travel.verify.mjs +72 -0
  93. package/index.js +7 -3
  94. package/lib/core/config-validator.js +37 -2
  95. package/lib/core/context.js +4 -2
  96. package/lib/core/dungeon-loader.js +20 -15
  97. package/lib/core/dungeon-to-json.js +22 -4
  98. package/lib/core/storage.js +3 -3
  99. package/lib/generators/funnels.js +36 -9
  100. package/lib/hook-helpers/cohort.js +53 -0
  101. package/lib/hook-helpers/index.js +8 -0
  102. package/lib/hook-helpers/mutate.js +1 -1
  103. package/lib/hook-helpers/shape.js +319 -0
  104. package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
  105. package/lib/hook-patterns/attributed-by-source.js +60 -58
  106. package/lib/hook-patterns/frequency-by-frequency.js +14 -4
  107. package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
  108. package/lib/hook-patterns/index.js +1 -1
  109. package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
  110. package/lib/orchestrators/user-loop.js +41 -0
  111. package/lib/templates/story-spec.schema.json +175 -0
  112. package/lib/utils/json-evaluator.js +1 -1
  113. package/lib/utils/utils.js +151 -59
  114. package/lib/verify/coerce.js +186 -0
  115. package/lib/verify/counting.js +270 -32
  116. package/lib/verify/emulate-breakdown.js +1285 -203
  117. package/lib/verify/first-time.js +89 -0
  118. package/lib/verify/flows.js +679 -0
  119. package/lib/verify/formula.js +259 -0
  120. package/lib/verify/funnel-engine.js +697 -113
  121. package/lib/verify/index.js +32 -4
  122. package/lib/verify/sessionize.js +275 -0
  123. package/lib/verify/story-runner.js +483 -0
  124. package/lib/verify/verify-dungeon.js +59 -37
  125. package/package.json +2 -1
  126. package/scripts/extract-dungeon-schema.mjs +2 -2
  127. package/scripts/run-many.mjs +2 -2
  128. package/scripts/verify-runner.mjs +2 -2
  129. package/scripts/verify-stories.mjs +244 -0
  130. package/types.d.ts +298 -30
  131. package/dungeons/vertical/ai-platform.js +0 -818
  132. package/dungeons/vertical/community.js +0 -966
  133. package/dungeons/vertical/crypto.js +0 -895
  134. package/dungeons/vertical/dating.js +0 -830
  135. package/dungeons/vertical/devtools.js +0 -1050
  136. package/dungeons/vertical/ecommerce.js +0 -811
  137. package/dungeons/vertical/education.js +0 -964
  138. package/dungeons/vertical/fintech.js +0 -946
  139. package/dungeons/vertical/fitness.js +0 -935
  140. package/dungeons/vertical/food-delivery.js +0 -934
  141. package/dungeons/vertical/gaming.js +0 -1120
  142. package/dungeons/vertical/healthcare.js +0 -945
  143. package/dungeons/vertical/insurance-application.js +0 -1039
  144. package/dungeons/vertical/logistics.js +0 -986
  145. package/dungeons/vertical/marketplace.js +0 -1019
  146. package/dungeons/vertical/real-estate.js +0 -872
  147. package/dungeons/vertical/sass.js +0 -995
  148. package/dungeons/vertical/social.js +0 -904
  149. package/dungeons/vertical/travel.js +0 -930
@@ -1,966 +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
- /** @typedef {import("../../types").Dungeon} Config */
9
-
10
- // ── OVERVIEW ──
11
- /*
12
- * NAME: FanVerse
13
- * APP: Fan wiki and community discussion platform where users create
14
- * articles, discuss topics, moderate content, and build collaborative
15
- * knowledge bases across fandoms. Core loop: sign up → search → read
16
- * articles → contribute → discuss. Revenue: free / supporter ($4.99,
17
- * ad-free) / pro ($12.99, analytics + badges).
18
- * SCALE: 10,000 users, ~1.4M events, 121 days (2026-01-01 → 2026-05-01)
19
- * CORE LOOP: account created → search performed → article viewed → article published → comment posted
20
- *
21
- * EVENTS (18):
22
- * article viewed (9) > app session (8) > upvote given (7) > comment posted (6)
23
- * > search performed (6) > notification received (6) > discussion posted (5)
24
- * > article edited (4) > article published (3) > user followed (3)
25
- * > wiki page created (2) > media uploaded (2) > moderation action (2)
26
- * > profile updated (2) > account created (1) > support ticket created (1)
27
- * > report submitted (1) > account deactivated (1)
28
- *
29
- * FUNNELS (5):
30
- * - Onboarding Flow: account created → search performed → article viewed → discussion posted (40%)
31
- * - Content Creation: article viewed → article published → comment posted (35%)
32
- * - Engagement Loop: article viewed → upvote given → comment posted → discussion posted (30%)
33
- * - Creator to Supporter: article published → profile updated → notification received (45%)
34
- * - Moderation Pipeline: report submitted → moderation action (60%)
35
- *
36
- * USER PROPS: role, contributor_level, articles_created, reputation_score, preferred_hub, subscription_tier, Platform, content_hub
37
- * SUPER PROPS: subscription_tier, Platform, content_hub
38
- * SCD PROPS: contributor_level (newcomer/regular/trusted/admin, monthly fuzzy, max 8)
39
- * GROUPS: none
40
- */
41
-
42
- // ── HOOK STORIES ──
43
- /*
44
- * -------------------------------------------------------------------
45
- * 1. WEEKEND CONTENT SURGE (event hook)
46
- * -------------------------------------------------------------------
47
- *
48
- * PATTERN: Articles published on weekends (Sat/Sun) have 1.5x
49
- * word_count. Creators have more time on weekends to write longer,
50
- * more detailed wiki articles.
51
- *
52
- * HOW TO FIND IT IN MIXPANEL:
53
- *
54
- * Report 1: Weekend vs Weekday Word Count
55
- * - Report type: Insights
56
- * - Event: "article published"
57
- * - Measure: Average of "word_count"
58
- * - Breakdown: Day of Week
59
- * - Expected: Sat/Sun ~ 1.5x avg word_count vs weekdays
60
- * (weekend ~ 3325, weekday ~ 2218)
61
- *
62
- * REAL-WORLD ANALOGUE: Community wikis see longer, more thoughtful
63
- * contributions on weekends when creators have uninterrupted time.
64
- *
65
- * -------------------------------------------------------------------
66
- * 2. TRENDING TOPIC WINDOW (event hook)
67
- * -------------------------------------------------------------------
68
- *
69
- * PATTERN: During days 35-50, articles in the "gaming" hub get 2x
70
- * view_count. Simulates a major game release driving traffic to
71
- * gaming wiki pages.
72
- *
73
- * HOW TO FIND IT IN MIXPANEL:
74
- *
75
- * Report 1: Gaming Hub View Spike
76
- * - Report type: Insights
77
- * - Event: "article viewed"
78
- * - Measure: Average of "view_count"
79
- * - Filter: content_hub = "gaming"
80
- * - Line chart by week
81
- * - Expected: Clear spike during days 35-50 (~2x normal)
82
- *
83
- * Report 2: Hub Comparison During Trend Window
84
- * - Report type: Insights
85
- * - Event: "article viewed"
86
- * - Measure: Average of "view_count"
87
- * - Breakdown: "content_hub"
88
- * - Filter: time within trend window
89
- * - Expected: gaming ~2x vs other hubs
90
- *
91
- * REAL-WORLD ANALOGUE: Major franchise releases (game launches,
92
- * movie premieres) drive massive traffic spikes to related wikis.
93
- *
94
- * -------------------------------------------------------------------
95
- * 3. POWER CREATOR ENGAGEMENT LIFT (everything hook)
96
- * -------------------------------------------------------------------
97
- *
98
- * PATTERN: Users who published >20 articles get 3x avg upvote_count
99
- * on their content events. Prolific creators earn community trust
100
- * and visibility, amplifying their engagement metrics.
101
- *
102
- * HOW TO FIND IT IN MIXPANEL:
103
- *
104
- * Report 1: Upvote Count by Creator Volume
105
- * - Report type: Insights
106
- * - Event: "upvote given"
107
- * - Measure: Average of "upvote_count"
108
- * - Breakdown: user property "role"
109
- * - Expected: creators (high volume) show ~3x upvote_count
110
- * (creators ~ 15, others ~ 5)
111
- *
112
- * REAL-WORLD ANALOGUE: Power contributors on platforms like Fandom
113
- * and Wikipedia earn disproportionate engagement due to reputation
114
- * and content quality.
115
- *
116
- * -------------------------------------------------------------------
117
- * 4. DISCUSSION DEPTH BY CONTRIBUTOR TYPE (everything hook)
118
- * -------------------------------------------------------------------
119
- *
120
- * PATTERN: Active contributors (segment "active_contributor") get
121
- * cloned comment_posted events to simulate deeper discussion threads.
122
- * Each existing comment has a 50% chance of spawning a follow-up.
123
- *
124
- * HOW TO FIND IT IN MIXPANEL:
125
- *
126
- * Report 1: Comments Per User by Segment
127
- * - Report type: Insights
128
- * - Event: "comment posted"
129
- * - Measure: Total per user
130
- * - Breakdown: user property "segment"
131
- * - Expected: active_contributor ~1.5x comments vs others
132
- *
133
- * REAL-WORLD ANALOGUE: Engaged contributors create deeper discussion
134
- * threads, replying to comments and fostering community dialogue.
135
- *
136
- * -------------------------------------------------------------------
137
- * 5. EDIT WAR DETECTION (everything hook)
138
- * -------------------------------------------------------------------
139
- *
140
- * PATTERN: Users with >5 rapid article_edited events within a short
141
- * window get reduced edit_quality score (set to 1-2 range vs normal
142
- * 1-5). Simulates contentious edits degrading quality.
143
- *
144
- * HOW TO FIND IT IN MIXPANEL:
145
- *
146
- * Report 1: Edit Quality by Volume
147
- * - Report type: Insights
148
- * - Event: "article edited"
149
- * - Measure: Average of "edit_quality"
150
- * - Breakdown: user property "segment"
151
- * - Expected: Users with many edits show lower avg quality
152
- * (high-edit users ~ 1.5 vs normal ~ 3.0)
153
- *
154
- * REAL-WORLD ANALOGUE: Wiki edit wars (e.g., Wikipedia) degrade
155
- * content quality as users repeatedly override each other's changes.
156
- *
157
- * -------------------------------------------------------------------
158
- * 6. LURKER CHURN (everything hook)
159
- * -------------------------------------------------------------------
160
- *
161
- * PATTERN: Users with <5 total events lose 60% of events after
162
- * day 10 of their activity. Simulates lurkers quickly losing
163
- * interest and churning out.
164
- *
165
- * HOW TO FIND IT IN MIXPANEL:
166
- *
167
- * Report 1: Event Volume by Segment Over Time
168
- * - Report type: Insights
169
- * - Event: Any event
170
- * - Measure: Total per user
171
- * - Breakdown: user property "segment"
172
- * - Line chart by week
173
- * - Expected: lurker segment drops off sharply after first 10 days
174
- *
175
- * REAL-WORLD ANALOGUE: Most community platforms see >60% of new
176
- * signups become inactive within the first 2-3 weeks.
177
- *
178
- * -------------------------------------------------------------------
179
- * 7. CREATOR PROFILES (user hook)
180
- * -------------------------------------------------------------------
181
- *
182
- * PATTERN: Users with role "creator" get articles_created set to
183
- * 50-200 range and reputation_score to 80-100. Moderators get
184
- * mid-range reputation. Readers/lurkers stay at defaults.
185
- *
186
- * HOW TO FIND IT IN MIXPANEL:
187
- *
188
- * Report 1: Reputation Distribution by Role
189
- * - Report type: Insights
190
- * - Event: any event
191
- * - Measure: Average of user property "reputation_score"
192
- * - Breakdown: user property "role"
193
- * - Expected: creator ~ 90, moderator ~ 55, reader ~ 25
194
- *
195
- * Report 2: Articles Created by Role
196
- * - Report type: Insights
197
- * - Measure: Average of user property "articles_created"
198
- * - Breakdown: user property "role"
199
- * - Expected: creator ~ 125, others ~ 0
200
- *
201
- * REAL-WORLD ANALOGUE: Top wiki contributors have hundreds of
202
- * articles and high community reputation scores.
203
- *
204
- * -------------------------------------------------------------------
205
- * 8. PRO SUBSCRIBER CONTENT CREATION LIFT (everything hook)
206
- * -------------------------------------------------------------------
207
- *
208
- * PATTERN: Free-tier users drop 50% of final funnel step events
209
- * ("comment posted"), creating a visible conversion gap between
210
- * paid and free users. Pro/supporter users keep all their events.
211
- *
212
- * HOW TO FIND IT IN MIXPANEL:
213
- *
214
- * Report 1: Content Creation Conversion by Tier
215
- * - Report type: Funnels
216
- * - Steps: "article viewed" -> "article published" -> "comment posted"
217
- * - Breakdown: "subscription_tier" (superProp)
218
- * - Expected: pro ~ 52% vs free ~ 26% conversion (~2x gap)
219
- *
220
- * REAL-WORLD ANALOGUE: Premium wiki tools (analytics dashboards,
221
- * badge systems) incentivize more content creation from subscribers.
222
- *
223
- * -------------------------------------------------------------------
224
- * 9. CONTENT CREATION TIME-TO-CONVERT (funnel-post hook)
225
- * -------------------------------------------------------------------
226
- *
227
- * PATTERN: Pro/supporter subscribers complete the Content Creation
228
- * funnel 1.3x faster (time gaps scaled by 0.77). Free-tier users
229
- * complete it 1.25x slower (gaps scaled by 1.25). The hook iterates
230
- * over the funnel-post event array, compresses or stretches the
231
- * inter-step time gaps based on the user's subscription_tier from
232
- * meta.profile, then rewrites each event's timestamp.
233
- *
234
- * HOW TO FIND IT IN MIXPANEL:
235
- *
236
- * Report 1: Content Creation TTC by Subscription Tier
237
- * - Report type: Funnels
238
- * - Steps: "article viewed" -> "article published" -> "comment posted"
239
- * - Breakdown: "subscription_tier" (superProp)
240
- * - Metric: Median time to convert
241
- * - Expected: pro/supporter median TTC ~ 0.77x of free-tier TTC
242
- * (e.g., pro ~ 27h vs free ~ 45h)
243
- *
244
- * NOTE: This effect is visible ONLY in Mixpanel funnel median TTC.
245
- * Cross-event MIN->MIN SQL queries on raw events do NOT show this
246
- * because funnel-post mutates timestamps after event generation but
247
- * before storage.
248
- *
249
- * REAL-WORLD ANALOGUE: Premium wiki contributors with analytics
250
- * dashboards and streamlined tools move from reading to publishing
251
- * faster; free users hesitate longer without feedback loops.
252
- *
253
- * -------------------------------------------------------------------
254
- * 10. ARTICLE-PUBLISHED MAGIC NUMBER (everything hook)
255
- * -------------------------------------------------------------------
256
- *
257
- * PATTERN: Users who published 2-5 articles sit in a "sweet spot" --
258
- * all their upvote_count values on "upvote given" events are boosted
259
- * by +35% (factor 1.35). Users who published 6+ articles trigger
260
- * over-publishing fatigue: 25% of their "upvote given" events are
261
- * dropped entirely. No flag is stamped -- discoverable only by
262
- * binning users on article-published COUNT and comparing upvote
263
- * totals or upvote event volume.
264
- *
265
- * HOW TO FIND IT IN MIXPANEL:
266
- *
267
- * Report 1: Upvote Count by Article Volume Cohort
268
- * - Report type: Insights (with cohorts)
269
- * - Cohort A: users who did "article published" 2-5 times
270
- * - Cohort B: users who did "article published" 0-1 times
271
- * - Event: "upvote given"
272
- * - Measure: Average of "upvote_count"
273
- * - Compare cohort A vs cohort B
274
- * - Expected: cohort A ~ 1.35x higher avg upvote_count
275
- *
276
- * Report 2: Upvote Events per User by Article Volume
277
- * - Report type: Insights (with cohorts)
278
- * - Cohort C: users who did "article published" 6+ times
279
- * - Cohort A: users who did "article published" 2-5 times
280
- * - Event: "upvote given"
281
- * - Measure: Total events per user
282
- * - Compare cohort C vs cohort A
283
- * - Expected: cohort C ~ 25% fewer upvote events per user
284
- *
285
- * REAL-WORLD ANALOGUE: Creators who publish a handful of quality
286
- * articles earn outsized community engagement; over-publishers
287
- * dilute their signal and get less traction per piece.
288
- *
289
- * ===================================================================
290
- * EXPECTED METRICS SUMMARY
291
- * ===================================================================
292
- *
293
- * Hook | Metric | Baseline | Effect | Ratio
294
- * ------------------------------|---------------------|----------|---------|------
295
- * Weekend Content Surge | word_count | 1500 | 2250 | 1.5x
296
- * Trending Topic Window | view_count (gaming) | 50 | 100 | 2x
297
- * Power Creator Engagement | upvote_count | 5 | 15 | 3x
298
- * Discussion Depth | comments/user | 3 | 4.5 | 1.5x
299
- * Edit War Detection | edit_quality | 3.0 | 1.5 | 0.5x
300
- * Lurker Churn | events after day 10 | 100% | 40% | 0.4x
301
- * Creator Profiles | reputation_score | 25 | 90 | 3.6x
302
- * Pro Content Creation Lift | funnel conversion | ~26% | 52% | ~2.0x
303
- * Content Creation TTC | funnel median TTC | 1x | 0.77x | 1.3x faster (pro)
304
- * Article-Published Magic Num | sweet upvote_count | 1x | 1.35x | +35%
305
- * Article-Published Magic Num | over upvote events | 1x | 0.75x | -25%
306
- */
307
-
308
- // ── SCALE ──
309
- const SEED = "dm4-community";
310
- const NUM_USERS = 10_000;
311
- const DATASET_START = "2026-01-01T00:00:00Z";
312
- const DATASET_END = "2026-05-01T23:59:59Z";
313
- const EVENTS_PER_DAY = 1.2;
314
- const token = process.env.MP_TOKEN || "your-mixpanel-token";
315
-
316
- const chance = u.initChance(SEED);
317
-
318
- // ── KNOBS (tweak these to reshape stories) ──
319
- const WEEKEND_WORD_COUNT_MULT = 1.5;
320
-
321
- const TREND_START_DAY = 35;
322
- const TREND_END_DAY = 50;
323
- const TREND_VIEW_MULT = 2;
324
-
325
- const POWER_CREATOR_PUBLISH_THRESHOLD = 20;
326
- const POWER_CREATOR_UPVOTE_MULT = 3;
327
-
328
- const DISCUSSION_CLONE_LIKELIHOOD = 50;
329
-
330
- const EDIT_WAR_THRESHOLD = 5;
331
- const EDIT_WAR_QUALITY_MIN = 1.0;
332
- const EDIT_WAR_QUALITY_MAX = 2.0;
333
-
334
- const LURKER_EVENT_THRESHOLD = 5;
335
- const LURKER_CHURN_CUTOFF_DAYS = 10;
336
- const LURKER_DROP_LIKELIHOOD = 60;
337
-
338
- const PRO_LIFT_FREE_DROP_LIKELIHOOD = 65;
339
-
340
- const TTC_PRO_FACTOR = 0.77;
341
- const TTC_FREE_FACTOR = 1.25;
342
-
343
- const ARTICLE_SWEET_MIN = 2;
344
- const ARTICLE_SWEET_MAX = 5;
345
- const ARTICLE_OVER_THRESHOLD = 6;
346
- const ARTICLE_UPVOTE_BOOST = 1.35;
347
- const ARTICLE_UPVOTE_DROP_LIKELIHOOD = 25;
348
-
349
- // ── DATA ARRAYS ──
350
- // Generate consistent wiki/article IDs at module level
351
- const wikiIds = v.range(1, 500).map(() => `WIKI_${v.uid(6)}`);
352
- const communityIds = v.range(1, 30).map(() => `COMM_${v.uid(4)}`);
353
-
354
- // ── HELPER FUNCTIONS ──
355
- function handleUserHooks(record) {
356
- // H7: CREATOR PROFILES — creators get high articles_created and reputation.
357
- // Moderators get mid-range reputation. Readers/lurkers stay at defaults.
358
- if (record.role === "creator") {
359
- record.articles_created = chance.integer({ min: 50, max: 200 });
360
- record.reputation_score = chance.integer({ min: 80, max: 100 });
361
- record.contributor_level = "admin";
362
- } else if (record.role === "moderator") {
363
- record.articles_created = chance.integer({ min: 10, max: 50 });
364
- record.reputation_score = chance.integer({ min: 40, max: 70 });
365
- record.contributor_level = "trusted";
366
- } else if (record.role === "contributor") {
367
- record.articles_created = chance.integer({ min: 1, max: 15 });
368
- record.reputation_score = chance.integer({ min: 15, max: 50 });
369
- record.contributor_level = "regular";
370
- } else {
371
- record.articles_created = 0;
372
- record.reputation_score = chance.integer({ min: 0, max: 20 });
373
- record.contributor_level = "newcomer";
374
- }
375
- return record;
376
- }
377
-
378
- function handleFunnelPostHooks(record, meta) {
379
- // H9: CONTENT CREATION TIME-TO-CONVERT — Pro/supporter complete 1.3x
380
- // faster (factor 0.77); Free 1.25x slower (factor 1.25).
381
- const segment = meta?.profile?.subscription_tier;
382
- if (Array.isArray(record) && record.length > 1) {
383
- const factor = (
384
- segment === "pro" || segment === "supporter" ? TTC_PRO_FACTOR :
385
- segment === "free" ? TTC_FREE_FACTOR :
386
- 1.0
387
- );
388
- if (factor !== 1.0) {
389
- for (let i = 1; i < record.length; i++) {
390
- const prev = dayjs(record[i - 1].time);
391
- const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
392
- record[i].time = prev.add(newGap, "milliseconds").toISOString();
393
- }
394
- }
395
- }
396
- return record;
397
- }
398
-
399
- function handleEverythingHooks(record, meta) {
400
- const datasetStart = dayjs.unix(meta.datasetStart);
401
- let events = record;
402
- if (!events.length) return record;
403
- const profile = meta && meta.profile ? meta.profile : {};
404
-
405
- // -- SUPERPROP STAMPING -----------------------------------
406
- // Stamp superProp values from profile onto every event so
407
- // they stay consistent per-user instead of randomizing per-event.
408
- events.forEach(e => {
409
- if (profile.subscription_tier) e.subscription_tier = profile.subscription_tier;
410
- if (profile.Platform) e.Platform = profile.Platform;
411
- if (profile.content_hub) e.content_hub = profile.content_hub;
412
- });
413
-
414
- // HOOK 1: WEEKEND CONTENT SURGE — articles on Sat/Sun get
415
- // word_count 1.5x. Mutates raw prop. No flag.
416
- for (const e of events) {
417
- if (e.event === 'article published' || e.event === 'article viewed') {
418
- const dow = new Date(e.time).getUTCDay();
419
- if ((dow === 0 || dow === 6) && e.word_count) {
420
- e.word_count = Math.floor(e.word_count * WEEKEND_WORD_COUNT_MULT);
421
- }
422
- }
423
- }
424
-
425
- // -- HOOK 2: TRENDING TOPIC WINDOW -------------------------
426
- // Days 35-50: gaming hub articles get 2x view_count.
427
- // Runs after superProp stamping so content_hub is the
428
- // profile's consistent value, not the random event-level one.
429
- const TREND_START = datasetStart.add(TREND_START_DAY, "days");
430
- const TREND_END = datasetStart.add(TREND_END_DAY, "days");
431
- if (profile.content_hub === "gaming") {
432
- events.forEach(e => {
433
- if (e.event === "article viewed") {
434
- const eventTime = dayjs(e.time);
435
- if (eventTime.isAfter(TREND_START) && eventTime.isBefore(TREND_END)) {
436
- e.view_count = Math.floor((e.view_count || 50) * TREND_VIEW_MULT);
437
- }
438
- }
439
- });
440
- }
441
-
442
- // -- HOOK 8: PRO SUBSCRIBER CONTENT CREATION LIFT ---------
443
- // Free-tier users drop 65% of comment events to widen the funnel
444
- // conversion gap to ~2x vs paid subscribers.
445
- if (profile.subscription_tier !== "pro" && profile.subscription_tier !== "supporter") {
446
- events = events.filter(e => {
447
- if (e.event === "comment posted" && chance.bool({ likelihood: PRO_LIFT_FREE_DROP_LIKELIHOOD })) return false;
448
- return true;
449
- });
450
- }
451
-
452
- // -- HOOK 3: POWER CREATOR ENGAGEMENT LIFT ----------------
453
- // Users with >20 article_published events get 3x upvote_count.
454
- let publishCount = 0;
455
- events.forEach(e => {
456
- if (e.event === "article published") publishCount++;
457
- });
458
-
459
- if (publishCount > POWER_CREATOR_PUBLISH_THRESHOLD) {
460
- events.forEach(e => {
461
- if (e.event === "upvote given" && e.upvote_count) {
462
- e.upvote_count = Math.floor(e.upvote_count * POWER_CREATOR_UPVOTE_MULT);
463
- }
464
- });
465
- }
466
-
467
- // -- HOOK 4: DISCUSSION DEPTH BY CONTRIBUTOR TYPE ---------
468
- // Active contributors get cloned comment_posted events.
469
- if (profile.segment === "active_contributor") {
470
- const templateComment = events.find(e => e.event === "comment posted");
471
- if (templateComment) {
472
- const existingComments = events.filter(e => e.event === "comment posted");
473
- existingComments.forEach(c => {
474
- if (chance.bool({ likelihood: DISCUSSION_CLONE_LIKELIHOOD })) {
475
- events.push({
476
- ...templateComment,
477
- time: dayjs(c.time).add(chance.integer({ min: 1, max: 120 }), "minutes").toISOString(),
478
- user_id: c.user_id,
479
- is_reply: true,
480
- comment_length: chance.integer({ min: 20, max: 300 }),
481
- });
482
- }
483
- });
484
- }
485
- }
486
-
487
- // -- HOOK 5: EDIT WAR DETECTION ---------------------------
488
- // Users with >5 article_edited events get reduced edit_quality.
489
- const editEvents = events.filter(e => e.event === "article edited");
490
- if (editEvents.length > EDIT_WAR_THRESHOLD) {
491
- editEvents.forEach(e => {
492
- e.edit_quality = chance.floating({ min: EDIT_WAR_QUALITY_MIN, max: EDIT_WAR_QUALITY_MAX, fixed: 1 });
493
- });
494
- }
495
-
496
- // HOOK 6: LURKER CHURN — users with <5 events lose 60% after
497
- // day 10. No flag.
498
- if (events.length < LURKER_EVENT_THRESHOLD && events.length > 0) {
499
- const firstEventTime = dayjs(events[0].time);
500
- const cutoff = firstEventTime.add(LURKER_CHURN_CUTOFF_DAYS, "days");
501
- for (let i = events.length - 1; i >= 0; i--) {
502
- if (dayjs(events[i].time).isAfter(cutoff) && chance.bool({ likelihood: LURKER_DROP_LIKELIHOOD })) {
503
- events.splice(i, 1);
504
- }
505
- }
506
- }
507
-
508
- // HOOK 10: ARTICLE-PUBLISHED MAGIC NUMBER (no flags)
509
- // Sweet 2-5 articles published → +35% on upvote_count for
510
- // upvote-given events. Over 6+ → drop 25% of upvote-given events
511
- // (over-publishing dilutes signal). No flag.
512
- const articleCount = events.filter(e => e.event === "article published").length;
513
- if (articleCount >= ARTICLE_SWEET_MIN && articleCount <= ARTICLE_SWEET_MAX) {
514
- events.forEach(e => {
515
- if (e.event === "upvote given" && typeof e.upvote_count === "number") {
516
- e.upvote_count = Math.round(e.upvote_count * ARTICLE_UPVOTE_BOOST);
517
- }
518
- });
519
- } else if (articleCount >= ARTICLE_OVER_THRESHOLD) {
520
- for (let i = events.length - 1; i >= 0; i--) {
521
- if (events[i].event === "upvote given" && chance.bool({ likelihood: ARTICLE_UPVOTE_DROP_LIKELIHOOD })) {
522
- events.splice(i, 1);
523
- }
524
- }
525
- }
526
-
527
- return events;
528
- }
529
-
530
- // ── CONFIG ──
531
- /** @type {Config} */
532
- const config = {
533
- version: 2,
534
- seed: SEED,
535
- datasetStart: DATASET_START,
536
- datasetEnd: DATASET_END,
537
- avgEventsPerUserPerDay: EVENTS_PER_DAY,
538
- numUsers: NUM_USERS,
539
- format: "json",
540
- gzip: true,
541
- credentials: {
542
- token,
543
- },
544
- switches: {
545
- hasSessionIds: true,
546
- alsoInferFunnels: false,
547
- hasLocation: true,
548
- hasAndroidDevices: true,
549
- hasIOSDevices: true,
550
- hasDesktopDevices: true,
551
- hasBrowser: false,
552
- hasCampaigns: false,
553
- isAnonymous: false,
554
- hasAdSpend: false,
555
- hasAvatar: true,
556
- },
557
- identity: {
558
- avgDevicePerUser: 2,
559
- },
560
- concurrency: 1,
561
- writeToDisk: false,
562
- scdProps: {
563
- contributor_level: {
564
- values: ["newcomer", "regular", "trusted", "admin"],
565
- frequency: "month",
566
- timing: "fuzzy",
567
- max: 8
568
- }
569
- },
570
- mirrorProps: {},
571
- lookupTables: [],
572
-
573
- // -- Events (18) --------------------------------------------------
574
- events: [
575
- {
576
- event: "account created",
577
- weight: 1,
578
- isFirstEvent: true,
579
- isAuthEvent: true,
580
- properties: {
581
- referral_source: ["organic", "google_search", "reddit_referral", "youtube_link", "friend_invite"],
582
- },
583
- },
584
- {
585
- event: "article viewed",
586
- weight: 9,
587
- isStrictEvent: false,
588
- properties: {
589
- wiki_id: chance.pickone.bind(chance, wikiIds),
590
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
591
- view_count: u.weighNumRange(1, 100, 0.4, 50),
592
- time_on_page_sec: u.weighNumRange(5, 600, 0.4, 45),
593
- },
594
- },
595
- {
596
- event: "article published",
597
- weight: 3,
598
- isStrictEvent: false,
599
- properties: {
600
- wiki_id: chance.pickone.bind(chance, wikiIds),
601
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
602
- word_count: u.weighNumRange(200, 5000, 0.4, 1500),
603
- has_images: [true, true, true, false],
604
- category: ["lore", "character", "episode_guide", "review", "tutorial", "news"],
605
- },
606
- },
607
- {
608
- event: "article edited",
609
- weight: 4,
610
- properties: {
611
- wiki_id: chance.pickone.bind(chance, wikiIds),
612
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
613
- edit_type: ["content", "formatting", "grammar", "citation", "revert"],
614
- edit_quality: u.weighNumRange(1, 5, 0.8, 3),
615
- chars_changed: u.weighNumRange(5, 2000, 0.3, 150),
616
- },
617
- },
618
- {
619
- event: "discussion posted",
620
- weight: 5,
621
- isStrictEvent: false,
622
- properties: {
623
- community_id: chance.pickone.bind(chance, communityIds),
624
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
625
- topic_type: ["theory", "question", "news", "review", "recommendation", "debate"],
626
- reply_count: u.weighNumRange(0, 50, 0.3, 5),
627
- discussion_mode: ["classic"],
628
- },
629
- },
630
- {
631
- event: "comment posted",
632
- weight: 6,
633
- isStrictEvent: false,
634
- properties: {
635
- community_id: chance.pickone.bind(chance, communityIds),
636
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
637
- comment_length: u.weighNumRange(10, 500, 0.4, 80),
638
- is_reply: [true, true, false],
639
- discussion_mode: ["classic"],
640
- },
641
- },
642
- {
643
- event: "upvote given",
644
- weight: 7,
645
- isStrictEvent: false,
646
- properties: {
647
- content_type: ["article", "article", "discussion", "comment"],
648
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
649
- upvote_count: u.weighNumRange(1, 10, 0.5, 5),
650
- },
651
- },
652
- {
653
- event: "search performed",
654
- weight: 6,
655
- properties: {
656
- search_term: ["walkthrough", "character list", "ending explained", "tier list", "release date", "easter eggs", "best builds", "lore timeline", "voice actors", "soundtrack"],
657
- results_count: u.weighNumRange(0, 50, 0.5, 12),
658
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
659
- },
660
- },
661
- {
662
- event: "wiki page created",
663
- weight: 2,
664
- properties: {
665
- wiki_id: chance.pickone.bind(chance, wikiIds),
666
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
667
- page_type: ["character", "location", "item", "episode", "concept", "organization"],
668
- word_count: u.weighNumRange(100, 3000, 0.4, 500),
669
- },
670
- },
671
- {
672
- event: "media uploaded",
673
- weight: 2,
674
- properties: {
675
- media_type: ["image", "image", "image", "gif", "video_clip", "screenshot"],
676
- file_size_kb: u.weighNumRange(50, 5000, 0.3, 500),
677
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
678
- },
679
- },
680
- {
681
- event: "moderation action",
682
- weight: 2,
683
- properties: {
684
- action_type: ["warn", "edit_revert", "content_removal", "user_mute", "spam_flag", "lock_thread"],
685
- severity: ["low", "low", "medium", "medium", "high"],
686
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
687
- resolution_time_hours: u.weighNumRange(0.1, 48, 0.3, 2),
688
- },
689
- },
690
- {
691
- event: "user followed",
692
- weight: 3,
693
- properties: {
694
- follow_source: ["profile", "article", "discussion", "recommendation"],
695
- },
696
- },
697
- {
698
- event: "notification received",
699
- weight: 6,
700
- properties: {
701
- notification_type: ["reply", "reply", "mention", "upvote", "follow", "article_update", "moderation"],
702
- channel: ["push", "push", "email", "in_app"],
703
- opened: [true, true, true, false],
704
- },
705
- },
706
- {
707
- event: "support ticket created",
708
- weight: 1,
709
- properties: {
710
- category: ["bug_report", "content_dispute", "account_issue", "feature_request", "abuse_report", "other"],
711
- priority: ["low", "low", "medium", "medium", "high"],
712
- resolution_hours: u.weighNumRange(1, 96, 0.4, 24),
713
- },
714
- },
715
- {
716
- event: "profile updated",
717
- weight: 2,
718
- properties: {
719
- field_updated: ["avatar", "bio", "display_name", "preferred_hub", "notification_settings", "badges"],
720
- },
721
- },
722
- {
723
- event: "app session",
724
- weight: 8,
725
- properties: {
726
- session_duration_sec: u.weighNumRange(10, 3600, 0.4, 180),
727
- pages_viewed: u.weighNumRange(1, 30, 0.5, 5),
728
- },
729
- },
730
- {
731
- event: "report submitted",
732
- weight: 1,
733
- properties: {
734
- report_type: ["spam", "harassment", "misinformation", "vandalism", "copyright", "other"],
735
- content_type: ["article", "comment", "discussion", "media"],
736
- },
737
- },
738
- {
739
- event: "account deactivated",
740
- weight: 1,
741
- isChurnEvent: true,
742
- returnLikelihood: 0.15,
743
- isStrictEvent: true,
744
- properties: {
745
- reason: ["lost_interest", "toxicity", "no_time", "switched_platform", "privacy_concern"],
746
- },
747
- },
748
- ],
749
-
750
- // -- Funnels (5) --------------------------------------------------
751
- funnels: [
752
- {
753
- name: "Onboarding Flow",
754
- sequence: ["account created", "search performed", "article viewed", "discussion posted"],
755
- conversionRate: 40,
756
- order: "sequential",
757
- isFirstFunnel: true,
758
- timeToConvert: 72,
759
- weight: 3,
760
- },
761
- {
762
- name: "Content Creation",
763
- sequence: ["article viewed", "article published", "comment posted"],
764
- conversionRate: 35,
765
- order: "sequential",
766
- timeToConvert: 48,
767
- weight: 5,
768
- },
769
- {
770
- name: "Engagement Loop",
771
- sequence: ["article viewed", "upvote given", "comment posted", "discussion posted"],
772
- conversionRate: 30,
773
- order: "sequential",
774
- timeToConvert: 96,
775
- weight: 4,
776
- reentry: true,
777
- },
778
- {
779
- name: "Creator to Supporter",
780
- sequence: ["article published", "profile updated", "notification received"],
781
- conversionRate: 45,
782
- order: "sequential",
783
- timeToConvert: 168,
784
- weight: 2,
785
- },
786
- {
787
- name: "Moderation Pipeline",
788
- sequence: ["report submitted", "moderation action"],
789
- conversionRate: 60,
790
- order: "sequential",
791
- timeToConvert: 48,
792
- weight: 2,
793
- },
794
- ],
795
-
796
- // -- SuperProps ----------------------------------------------------
797
- superProps: {
798
- subscription_tier: ["free", "free", "free", "free", "supporter", "pro"],
799
- Platform: ["ios", "android", "web"],
800
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
801
- },
802
-
803
- // -- UserProps -----------------------------------------------------
804
- userProps: {
805
- role: ["reader", "reader", "reader", "reader", "reader", "reader", "contributor", "contributor", "moderator", "creator"],
806
- contributor_level: ["newcomer"],
807
- articles_created: [0],
808
- reputation_score: u.weighNumRange(0, 100, 0.3, 25),
809
- preferred_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
810
- subscription_tier: ["free", "free", "free", "free", "supporter", "pro"],
811
- Platform: ["ios", "android", "web"],
812
- content_hub: ["gaming", "anime", "movies", "tv", "comics", "music"],
813
- },
814
-
815
- // -- Phase 2: Personas --------------------------------------------
816
- personas: [
817
- {
818
- name: "power_creator",
819
- weight: 5,
820
- eventMultiplier: 8.0,
821
- conversionModifier: 2.0,
822
- churnRate: 0.01,
823
- properties: {
824
- role: "creator",
825
- segment: "power_creator",
826
- },
827
- },
828
- {
829
- name: "moderator",
830
- weight: 8,
831
- eventMultiplier: 3.0,
832
- conversionModifier: 1.5,
833
- churnRate: 0.02,
834
- properties: {
835
- role: "moderator",
836
- segment: "moderator",
837
- },
838
- },
839
- {
840
- name: "active_contributor",
841
- weight: 25,
842
- eventMultiplier: 1.5,
843
- conversionModifier: 1.0,
844
- churnRate: 0.05,
845
- properties: {
846
- role: "contributor",
847
- segment: "active_contributor",
848
- },
849
- },
850
- {
851
- name: "reader",
852
- weight: 45,
853
- eventMultiplier: 0.3,
854
- conversionModifier: 0.5,
855
- churnRate: 0.12,
856
- properties: {
857
- role: "reader",
858
- segment: "reader",
859
- },
860
- },
861
- {
862
- name: "lurker",
863
- weight: 17,
864
- eventMultiplier: 0.1,
865
- conversionModifier: 0.2,
866
- churnRate: 0.5,
867
- properties: {
868
- role: "reader",
869
- segment: "lurker",
870
- },
871
- activeWindow: { maxDays: 21 },
872
- },
873
- ],
874
-
875
- // -- Phase 2: Subscription ----------------------------------------
876
- subscription: {
877
- plans: [
878
- { name: "free", price: 0, default: true },
879
- { name: "supporter", price: 4.99 },
880
- { name: "pro", price: 12.99 },
881
- ],
882
- lifecycle: {
883
- trialToPayRate: 0.25,
884
- upgradeRate: 0.06,
885
- downgradeRate: 0.04,
886
- churnRate: 0.06,
887
- winBackRate: 0.08,
888
- winBackDelay: 21,
889
- paymentFailureRate: 0.02,
890
- },
891
- },
892
-
893
- // -- Phase 2: Attribution -----------------------------------------
894
- attribution: {
895
- model: "last_touch",
896
- window: 7,
897
- campaigns: [
898
- {
899
- name: "google_search",
900
- source: "google",
901
- medium: "organic",
902
- activeDays: [0, 120],
903
- dailyBudget: [100, 400],
904
- acquisitionRate: 0.03,
905
- userPersonaBias: { reader: 0.5, lurker: 0.3 },
906
- },
907
- {
908
- name: "reddit_referral",
909
- source: "reddit",
910
- medium: "referral",
911
- activeDays: [0, 120],
912
- dailyBudget: [50, 200],
913
- acquisitionRate: 0.02,
914
- userPersonaBias: { active_contributor: 0.5, moderator: 0.3 },
915
- },
916
- {
917
- name: "youtube_link",
918
- source: "youtube",
919
- medium: "referral",
920
- activeDays: [0, 120],
921
- dailyBudget: [75, 250],
922
- acquisitionRate: 0.02,
923
- userPersonaBias: { lurker: 0.5, reader: 0.3 },
924
- },
925
- ],
926
- organicRate: 0.35,
927
- },
928
-
929
- // -- Phase 2: Engagement Decay ------------------------------------
930
- engagementDecay: {
931
- model: "linear",
932
- halfLife: 60,
933
- floor: 0.15,
934
- },
935
-
936
- // -- Phase 2: Features --------------------------------------------
937
- features: [
938
- {
939
- name: "live_discussions",
940
- launchDay: 25,
941
- adoptionCurve: "fast",
942
- property: "discussion_mode",
943
- values: ["classic", "live"],
944
- defaultBefore: "classic",
945
- affectsEvents: ["discussion posted", "comment posted"],
946
- },
947
- {
948
- name: "interactive_polls",
949
- launchDay: 55,
950
- adoptionCurve: { k: 0.1, midpoint: 20 },
951
- property: "poll_enabled",
952
- values: [false, true],
953
- defaultBefore: false,
954
- affectsEvents: ["article published"],
955
- },
956
- ],
957
-
958
- hook(record, type, meta) {
959
- if (type === "user") return handleUserHooks(record);
960
- if (type === "funnel-post") return handleFunnelPostHooks(record, meta);
961
- if (type === "everything") return handleEverythingHooks(record, meta);
962
- return record;
963
- },
964
- };
965
-
966
- export default config;