make-mp-data 2.1.11 → 3.0.2

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 (71) hide show
  1. package/README.md +31 -0
  2. package/dungeons/adspend.js +35 -1
  3. package/dungeons/anon.js +25 -1
  4. package/dungeons/array-of-object-lookup.js +201 -0
  5. package/dungeons/benchmark-heavy.js +241 -0
  6. package/dungeons/benchmark-light.js +141 -0
  7. package/dungeons/big.js +10 -9
  8. package/dungeons/business.js +60 -12
  9. package/dungeons/complex.js +35 -1
  10. package/dungeons/copilot.js +383 -0
  11. package/dungeons/education.js +1005 -0
  12. package/dungeons/experiments.js +18 -4
  13. package/dungeons/fintech.js +976 -0
  14. package/dungeons/foobar.js +32 -0
  15. package/dungeons/food.js +988 -0
  16. package/dungeons/funnels.js +38 -1
  17. package/dungeons/gaming.js +26 -5
  18. package/dungeons/media.js +861 -270
  19. package/dungeons/mil.js +31 -3
  20. package/dungeons/mirror.js +33 -1
  21. package/dungeons/retention-cadence.js +211 -0
  22. package/dungeons/rpg.js +1178 -0
  23. package/dungeons/sanity.js +32 -2
  24. package/dungeons/sass.js +923 -0
  25. package/dungeons/scd.js +47 -1
  26. package/dungeons/simple.js +29 -14
  27. package/dungeons/social.js +928 -0
  28. package/dungeons/streaming.js +373 -0
  29. package/dungeons/strict-event-test.js +30 -0
  30. package/dungeons/student-teacher.js +19 -5
  31. package/dungeons/text-generation.js +120 -84
  32. package/dungeons/too-big-events.js +203 -0
  33. package/dungeons/{userAgent.js → user-agent.js} +23 -2
  34. package/entry.js +5 -4
  35. package/index.js +41 -54
  36. package/lib/core/config-validator.js +122 -7
  37. package/lib/core/context.js +7 -14
  38. package/lib/core/storage.js +57 -25
  39. package/lib/generators/adspend.js +12 -12
  40. package/lib/generators/events.js +6 -5
  41. package/lib/generators/funnels.js +32 -10
  42. package/lib/generators/product-lookup.js +262 -0
  43. package/lib/generators/product-names.js +195 -0
  44. package/lib/generators/profiles.js +3 -3
  45. package/lib/generators/scd.js +13 -3
  46. package/lib/generators/text.js +17 -4
  47. package/lib/orchestrators/mixpanel-sender.js +244 -204
  48. package/lib/orchestrators/user-loop.js +54 -16
  49. package/lib/templates/phrases.js +473 -16
  50. package/lib/templates/schema.d.ts +173 -0
  51. package/lib/templates/verbose-schema.js +140 -206
  52. package/lib/utils/chart.js +210 -0
  53. package/lib/utils/function-registry.js +285 -0
  54. package/lib/utils/json-evaluator.js +172 -0
  55. package/lib/utils/logger.js +34 -0
  56. package/lib/utils/utils.js +41 -4
  57. package/package.json +12 -21
  58. package/types.d.ts +15 -5
  59. package/dungeons/ai-chat-analytics-ed.js +0 -274
  60. package/dungeons/money2020-ed-also.js +0 -277
  61. package/dungeons/money2020-ed.js +0 -579
  62. package/lib/generators/text-bak-old.js +0 -1121
  63. package/lib/orchestrators/worker-manager.js +0 -203
  64. package/lib/templates/hooks-instructions.txt +0 -434
  65. package/lib/templates/phrases-bak.js +0 -925
  66. package/lib/templates/prompt (old).txt +0 -98
  67. package/lib/templates/schema-instructions.txt +0 -155
  68. package/lib/templates/scratch-dungeon-template.js +0 -116
  69. package/lib/templates/textQuickTest.js +0 -172
  70. package/lib/utils/ai.js +0 -120
  71. package/lib/utils/project.js +0 -166
@@ -0,0 +1,976 @@
1
+ import dayjs from "dayjs";
2
+ import utc from "dayjs/plugin/utc.js";
3
+ import "dotenv/config";
4
+ import * as u from "../lib/utils/utils.js";
5
+
6
+ const SEED = "harness-fintech";
7
+ dayjs.extend(utc);
8
+ const chance = u.initChance(SEED);
9
+ const num_users = 5_000;
10
+ const days = 100;
11
+
12
+ /** @typedef {import("../../types.js").Dungeon} Config */
13
+
14
+ /**
15
+ * NEEDLE IN A HAYSTACK - NEOBANK APP DESIGN
16
+ *
17
+ * NexBank - A Chime/Revolut-style neobank app where users manage accounts, make transactions,
18
+ * send transfers, pay bills, set budgets, invest, apply for loans, and earn rewards.
19
+ *
20
+ * CORE USER LOOP:
21
+ * Users open accounts (personal or business) through one of four channels (app, web, referral,
22
+ * branch). They begin using the app to check balances, make transactions (purchases, ATM
23
+ * withdrawals, direct deposits, refunds), and send transfers (internal, external, P2P, wire).
24
+ * The app tracks spending patterns and encourages financial wellness through budgets, savings
25
+ * goals, and investment tools.
26
+ *
27
+ * ACCOUNT TIERS:
28
+ * Three-tiered system drives monetization and user segmentation:
29
+ * - Basic (free): Standard banking, limited rewards
30
+ * - Plus ($4.99/mo): Enhanced rewards, priority support, budgeting tools
31
+ * - Premium ($14.99/mo): 3x rewards, 2x investment returns, premium analytics
32
+ * Tiers are assigned as superProps, creating a persistent segmentation dimension.
33
+ *
34
+ * TRANSACTION ECOSYSTEM:
35
+ * Transactions are the heartbeat of the app. Seven merchant categories (grocery, restaurant,
36
+ * gas, retail, online, subscription, utilities) and four payment methods (debit, credit,
37
+ * contactless, online) model realistic spending. Transaction amounts follow a power-law
38
+ * distribution centered around $50, with occasional large purchases up to $5,000.
39
+ *
40
+ * FINANCIAL WELLNESS:
41
+ * Budget creation and alerts model the "financial health" feature set. Users create budgets
42
+ * across six categories with monthly limits, then receive alerts when approaching or exceeding
43
+ * limits. This creates a measurable engagement loop: budget creators save more and invest more
44
+ * (Hook #5: Budget Users Save More).
45
+ *
46
+ * SAVINGS & INVESTMENT:
47
+ * Savings goals (emergency, vacation, car, home, education, retirement) with target amounts
48
+ * and monthly contributions model long-term financial planning. Investment events (stocks, ETF,
49
+ * crypto, bonds, mutual funds) with buy/sell actions model portfolio activity.
50
+ *
51
+ * LENDING:
52
+ * Loan applications (personal, auto, home, student, business) flow through apply-to-approved
53
+ * funnel. Requested vs. approved amounts and interest rates create realistic lending analytics.
54
+ *
55
+ * SUPPORT & ENGAGEMENT:
56
+ * Support contacts across four channels (chat, phone, email, in-app) with resolution tracking.
57
+ * Notifications (transaction, low_balance, bill_due, reward, security, promo) with action
58
+ * tracking model re-engagement effectiveness.
59
+ *
60
+ * BILL PAYMENTS:
61
+ * Six bill types (rent, utilities, phone, insurance, subscription, loan_payment) with auto-pay
62
+ * toggle. Hook #6 (Auto-Pay Loyalty) creates a pattern where auto-pay users never miss
63
+ * payments while manual payers miss 30%, modeling real-world payment reliability.
64
+ *
65
+ * REWARDS:
66
+ * Four reward types (cashback, points, discount, partner_offer) with values scaled by
67
+ * account tier. Premium users earn 3x rewards (Hook #7), creating clear tier-based value
68
+ * differentiation visible in the data.
69
+ *
70
+ * HOUSEHOLD GROUPS:
71
+ * 500 households group users for shared financial analytics. Household-level properties
72
+ * (size, combined income, financial health score, primary bank) enable group-level analysis
73
+ * of shared financial behaviors.
74
+ *
75
+ * WHY THESE EVENTS/PROPERTIES?
76
+ * - Events model a complete banking loop: onboarding -> daily use -> financial planning -> monetization
77
+ * - Properties enable cohort analysis: account type, tier, income bracket, credit score
78
+ * - Funnels reveal friction: onboarding completion, transfer flows, investment journeys
79
+ * - Financial wellness features (budgets, goals) create engagement depth visible in the data
80
+ * - Tier-based rewards and investment returns drive business metric differences
81
+ * - The "needle in haystack" hooks simulate real fintech product insights hidden in production data
82
+ */
83
+
84
+ /** @type {Config} */
85
+ const config = {
86
+ token: "",
87
+ seed: SEED,
88
+ numDays: days,
89
+ numEvents: num_users * 120,
90
+ numUsers: num_users,
91
+ hasAnonIds: false,
92
+ hasSessionIds: true,
93
+ format: "json",
94
+ gzip: true,
95
+ alsoInferFunnels: false,
96
+ hasLocation: true,
97
+ hasAndroidDevices: true,
98
+ hasIOSDevices: true,
99
+ hasDesktopDevices: true,
100
+ hasBrowser: false,
101
+ hasCampaigns: false,
102
+ isAnonymous: false,
103
+ hasAdSpend: false,
104
+ percentUsersBornInDataset: 50,
105
+
106
+ hasAvatar: true,
107
+ makeChart: false,
108
+
109
+ batchSize: 2_500_000,
110
+ concurrency: 10,
111
+ writeToDisk: false,
112
+
113
+ scdProps: {},
114
+
115
+ funnels: [
116
+ {
117
+ sequence: ["account opened", "app session", "balance checked"],
118
+ isFirstFunnel: true,
119
+ conversionRate: 85,
120
+ timeToConvert: 0.25,
121
+ },
122
+ {
123
+ // Daily banking: check balance, view transactions - most common activity
124
+ sequence: ["app session", "balance checked", "transaction completed"],
125
+ conversionRate: 80,
126
+ timeToConvert: 0.5,
127
+ weight: 5,
128
+ },
129
+ {
130
+ // Transfers and notifications
131
+ sequence: ["app session", "transfer sent", "notification opened"],
132
+ conversionRate: 50,
133
+ timeToConvert: 1,
134
+ weight: 3,
135
+ },
136
+ {
137
+ // Bill payment flow
138
+ sequence: ["app session", "bill paid", "notification opened"],
139
+ conversionRate: 60,
140
+ timeToConvert: 1,
141
+ weight: 3,
142
+ },
143
+ {
144
+ // Financial planning: budgets and savings
145
+ sequence: ["budget created", "budget alert", "savings goal set"],
146
+ conversionRate: 40,
147
+ timeToConvert: 12,
148
+ weight: 2,
149
+ },
150
+ {
151
+ // Investment and rewards
152
+ sequence: ["balance checked", "investment made", "reward redeemed"],
153
+ conversionRate: 30,
154
+ timeToConvert: 5,
155
+ weight: 2,
156
+ },
157
+ {
158
+ // Support and account management
159
+ sequence: ["support contacted", "card locked", "dispute filed"],
160
+ conversionRate: 35,
161
+ timeToConvert: 2,
162
+ weight: 1,
163
+ },
164
+ {
165
+ // Lending flow
166
+ sequence: ["loan applied", "loan approved", "premium upgraded"],
167
+ conversionRate: 25,
168
+ timeToConvert: 10,
169
+ weight: 1,
170
+ },
171
+ ],
172
+
173
+ events: [
174
+ {
175
+ event: "account opened",
176
+ weight: 1,
177
+ isFirstEvent: true,
178
+ properties: {
179
+ "account_type": u.pickAWinner(["personal", "business", "personal"]),
180
+ "signup_channel": u.pickAWinner(["app", "web", "referral", "branch"]),
181
+ }
182
+ },
183
+ {
184
+ event: "app session",
185
+ weight: 20,
186
+ properties: {
187
+ "session_duration_sec": u.weighNumRange(10, 600, 0.3, 60),
188
+ "pages_viewed": u.weighNumRange(1, 15, 0.5, 3),
189
+ }
190
+ },
191
+ {
192
+ event: "balance checked",
193
+ weight: 15,
194
+ properties: {
195
+ "account_balance": u.weighNumRange(0, 50000, 0.8, 2500),
196
+ "account_type": u.pickAWinner(["checking", "savings", "investment"]),
197
+ }
198
+ },
199
+ {
200
+ event: "transaction completed",
201
+ weight: 18,
202
+ properties: {
203
+ "transaction_type": u.pickAWinner(["purchase", "atm", "direct_deposit", "refund"]),
204
+ "amount": u.weighNumRange(1, 5000, 0.3, 50),
205
+ "merchant_category": u.pickAWinner(["grocery", "restaurant", "gas", "retail", "online", "subscription", "utilities"]),
206
+ "payment_method": u.pickAWinner(["debit", "credit", "contactless", "online"]),
207
+ }
208
+ },
209
+ {
210
+ event: "transfer sent",
211
+ weight: 8,
212
+ properties: {
213
+ "transfer_type": u.pickAWinner(["internal", "external", "p2p", "wire"]),
214
+ "amount": u.weighNumRange(10, 10000, 0.3, 200),
215
+ "recipient_type": u.pickAWinner(["friend", "family", "business", "self"]),
216
+ }
217
+ },
218
+ {
219
+ event: "bill paid",
220
+ weight: 6,
221
+ properties: {
222
+ "bill_type": u.pickAWinner(["rent", "utilities", "phone", "insurance", "subscription", "loan_payment"]),
223
+ "amount": u.weighNumRange(20, 3000, 0.5, 150),
224
+ "auto_pay": u.pickAWinner([true, false], 0.4),
225
+ }
226
+ },
227
+ {
228
+ event: "budget created",
229
+ weight: 3,
230
+ properties: {
231
+ "category": u.pickAWinner(["food", "transport", "entertainment", "shopping", "bills", "savings"]),
232
+ "monthly_limit": u.weighNumRange(50, 2000, 0.5, 300),
233
+ }
234
+ },
235
+ {
236
+ event: "budget alert",
237
+ weight: 4,
238
+ properties: {
239
+ "alert_type": u.pickAWinner(["approaching_limit", "exceeded", "on_track"]),
240
+ "percent_used": u.weighNumRange(50, 150, 1, 90),
241
+ }
242
+ },
243
+ {
244
+ event: "savings goal set",
245
+ weight: 3,
246
+ properties: {
247
+ "goal_type": u.pickAWinner(["emergency", "vacation", "car", "home", "education", "retirement"]),
248
+ "target_amount": u.weighNumRange(500, 50000, 0.3, 5000),
249
+ "monthly_contribution": u.weighNumRange(25, 2000, 0.5, 200),
250
+ }
251
+ },
252
+ {
253
+ event: "investment made",
254
+ weight: 4,
255
+ properties: {
256
+ "investment_type": u.pickAWinner(["stocks", "etf", "crypto", "bonds", "mutual_fund"]),
257
+ "amount": u.weighNumRange(10, 10000, 0.3, 250),
258
+ "action": u.pickAWinner(["buy", "sell", "buy"]),
259
+ }
260
+ },
261
+ {
262
+ event: "card locked",
263
+ weight: 1,
264
+ properties: {
265
+ "reason": u.pickAWinner(["lost", "stolen", "suspicious_activity", "travel"]),
266
+ }
267
+ },
268
+ {
269
+ event: "dispute filed",
270
+ weight: 1,
271
+ properties: {
272
+ "dispute_amount": u.weighNumRange(10, 2000, 0.5, 100),
273
+ "reason": u.pickAWinner(["unauthorized", "duplicate", "not_received", "damaged", "wrong_amount"]),
274
+ }
275
+ },
276
+ {
277
+ event: "loan applied",
278
+ weight: 2,
279
+ properties: {
280
+ "loan_type": u.pickAWinner(["personal", "auto", "home", "student", "business"]),
281
+ "requested_amount": u.weighNumRange(1000, 100000, 0.3, 10000),
282
+ }
283
+ },
284
+ {
285
+ event: "loan approved",
286
+ weight: 1,
287
+ properties: {
288
+ "loan_type": u.pickAWinner(["personal", "auto", "home", "student", "business"]),
289
+ "approved_amount": u.weighNumRange(1000, 100000, 0.3, 10000),
290
+ "interest_rate": u.weighNumRange(3, 25, 1, 8),
291
+ }
292
+ },
293
+ {
294
+ event: "premium upgraded",
295
+ weight: 2,
296
+ properties: {
297
+ "old_tier": u.pickAWinner(["basic", "plus", "premium"]),
298
+ "new_tier": u.pickAWinner(["plus", "premium", "premium"]),
299
+ "monthly_fee": u.pickAWinner([4.99, 9.99, 14.99]),
300
+ }
301
+ },
302
+ {
303
+ event: "support contacted",
304
+ weight: 3,
305
+ properties: {
306
+ "channel": u.pickAWinner(["chat", "phone", "email", "in_app"]),
307
+ "issue_type": u.pickAWinner(["transaction", "account", "card", "transfer", "technical"]),
308
+ "resolved": u.pickAWinner([true, false], 0.8),
309
+ }
310
+ },
311
+ {
312
+ event: "notification opened",
313
+ weight: 10,
314
+ properties: {
315
+ "notification_type": u.pickAWinner(["transaction", "low_balance", "bill_due", "reward", "security", "promo"]),
316
+ "action_taken": u.pickAWinner([true, false], 0.6),
317
+ }
318
+ },
319
+ {
320
+ event: "reward redeemed",
321
+ weight: 4,
322
+ properties: {
323
+ "reward_type": u.pickAWinner(["cashback", "points", "discount", "partner_offer"]),
324
+ "value": u.weighNumRange(1, 100, 0.5, 10),
325
+ }
326
+ }
327
+ ],
328
+
329
+ superProps: {
330
+ account_tier: u.pickAWinner(["basic", "basic", "basic", "plus", "plus", "premium"]),
331
+ platform: u.pickAWinner(["ios", "android", "web"]),
332
+ },
333
+
334
+ userProps: {
335
+ "credit_score_range": u.pickAWinner(["300-579", "580-669", "670-739", "740-799", "800-850"]),
336
+ "income_bracket": u.pickAWinner(["under_30k", "30k_50k", "50k_75k", "75k_100k", "100k_150k", "over_150k"]),
337
+ "account_age_months": u.weighNumRange(1, 60, 0.5, 12),
338
+ "total_balance": u.weighNumRange(0, 100000, 0.3, 5000),
339
+ "has_direct_deposit": u.pickAWinner([true, false], 0.6),
340
+ },
341
+
342
+ groupKeys: [
343
+ ["household_id", 500, ["transaction completed", "transfer sent", "bill paid", "savings goal set"]],
344
+ ],
345
+
346
+ groupProps: {
347
+ household_id: {
348
+ "household_size": u.weighNumRange(1, 6),
349
+ "combined_income": u.weighNumRange(20000, 300000, 0.3, 75000),
350
+ "financial_health_score": u.weighNumRange(1, 100, 1, 65),
351
+ "primary_bank": u.pickAWinner(["NexBank_only", "multi_bank", "NexBank_only"]),
352
+ }
353
+ },
354
+
355
+ lookupTables: [],
356
+
357
+ /**
358
+ * ARCHITECTED ANALYTICS HOOKS
359
+ *
360
+ * This hook function creates 8 deliberate patterns in the data:
361
+ *
362
+ * 1. PERSONAL VS BUSINESS: Business accounts get employee_count, revenue; personal get age_range, life_stage
363
+ * 2. PAYDAY PATTERNS: Transactions spike on 1st/15th with bigger deposits and post-payday spending
364
+ * 3. FRAUD DETECTION: 3% of users experience a fraud burst (rapid high-value txns -> card lock -> dispute -> support)
365
+ * 4. LOW BALANCE CHURN: Users with chronic low balances (<$500) lose 50% of activity after day 30
366
+ * 5. BUDGET DISCIPLINE: Budget creators save 2x more and invest 1.5x more
367
+ * 6. AUTO-PAY LOYALTY: Auto-pay users never miss bills; manual payers miss 30%
368
+ * 7. PREMIUM TIER VALUE: Premium users get 3x rewards; Plus users get 1.5x; Premium investors get 2x returns
369
+ * 8. MONTH-END ANXIETY: Last 3 days of month see 40% longer sessions and 30% lower balances
370
+ */
371
+ hook: function (record, type, meta) {
372
+ const NOW = dayjs();
373
+ const DATASET_START = NOW.subtract(days, "days");
374
+
375
+ // ===============================================================
376
+ // Hook #1: PERSONAL VS BUSINESS ACCOUNTS (user)
377
+ // 20% of users are randomly tagged as business accounts with
378
+ // employee_count, annual_revenue, and industry. The other 80% get
379
+ // personal attributes: age_range and life_stage.
380
+ // ===============================================================
381
+ if (type === "user") {
382
+ const isBusiness = chance.bool({ likelihood: 20 });
383
+ if (isBusiness) {
384
+ record.account_segment = "business";
385
+ record.employee_count = chance.integer({ min: 5, max: 500 });
386
+ record.annual_revenue = chance.integer({ min: 100000, max: 10000000 });
387
+ record.industry = chance.pickone(["tech", "retail", "food", "services", "healthcare"]);
388
+ } else {
389
+ record.account_segment = "personal";
390
+ record.age_range = `${chance.pickone([18, 25, 35, 45, 55])}-${chance.pickone([24, 34, 44, 54, 65])}`;
391
+ record.life_stage = chance.pickone(["student", "early_career", "established", "pre_retirement", "retired"]);
392
+ }
393
+ }
394
+
395
+ // ===============================================================
396
+ // Hook #2: PAYDAY PATTERNS (event)
397
+ // On the 1st and 15th, direct deposit transactions are 2x bigger
398
+ // and tagged with payday: true. On days 1-3 and 15-17, transfers
399
+ // have a 40% chance of 1.5x boost (post-payday spending).
400
+ // ===============================================================
401
+ if (type === "event") {
402
+ const EVENT_TIME = dayjs(record.time);
403
+ const dayOfMonth = EVENT_TIME.date();
404
+
405
+ // Payday: 1st and 15th
406
+ if (record.event === "transaction completed" && record.transaction_type === "direct_deposit") {
407
+ if (dayOfMonth === 1 || dayOfMonth === 15) {
408
+ record.amount = Math.floor((record.amount || 50) * 3);
409
+ record.payday = true;
410
+ } else {
411
+ record.payday = false;
412
+ }
413
+ }
414
+
415
+ // Post-payday spending: days 1-3 and 15-17
416
+ if (record.event === "transfer sent") {
417
+ const isPaydayWindow = (dayOfMonth >= 1 && dayOfMonth <= 3) || (dayOfMonth >= 15 && dayOfMonth <= 17);
418
+ if (isPaydayWindow && chance.bool({ likelihood: 60 })) {
419
+ record.amount = Math.floor((record.amount || 200) * 2.0);
420
+ record.post_payday_spending = true;
421
+ } else {
422
+ record.post_payday_spending = false;
423
+ }
424
+ }
425
+
426
+ // ===============================================================
427
+ // Hook #6: AUTO-PAY LOYALTY (event)
428
+ // Users with auto_pay=false on bill paid events have a 30% chance
429
+ // of the event being dropped entirely (missed payment). Surviving
430
+ // manual payments get tagged. Auto-pay users always succeed.
431
+ // ===============================================================
432
+ if (record.event === "bill paid") {
433
+ if (record.auto_pay === false || record.auto_pay === undefined) {
434
+ if (chance.bool({ likelihood: 30 })) {
435
+ record.event = "bill payment missed";
436
+ record.missed_payment = true;
437
+ record.manual_payment = false;
438
+ } else {
439
+ record.missed_payment = false;
440
+ record.manual_payment = true;
441
+ }
442
+ } else {
443
+ record.missed_payment = false;
444
+ record.manual_payment = false;
445
+ }
446
+ }
447
+
448
+ // ===============================================================
449
+ // Hook #7: PREMIUM TIER VALUE (event)
450
+ // Premium tier users get 3x reward values and 2x investment sell
451
+ // returns. Plus tier users get 1.5x rewards.
452
+ // ===============================================================
453
+ if (record.event === "reward redeemed") {
454
+ if (record.account_tier === "premium") {
455
+ record.value = Math.floor((record.value || 10) * 3);
456
+ record.premium_reward = true;
457
+ } else if (record.account_tier === "plus") {
458
+ record.value = Math.floor((record.value || 10) * 1.5);
459
+ record.premium_reward = false;
460
+ } else {
461
+ record.premium_reward = false;
462
+ }
463
+ }
464
+
465
+ if (record.event === "investment made" && record.action === "sell") {
466
+ if (record.account_tier === "premium") {
467
+ record.amount = Math.floor((record.amount || 250) * 2);
468
+ record.premium_returns = true;
469
+ } else {
470
+ record.premium_returns = false;
471
+ }
472
+ }
473
+
474
+ // ===============================================================
475
+ // Hook #8: MONTH-END ANXIETY (event)
476
+ // Last 3 days of month (day >= 28) see 40% longer app sessions
477
+ // and 30% lower balances when checked.
478
+ // ===============================================================
479
+ if (record.event === "app session") {
480
+ if (dayOfMonth >= 28) {
481
+ record.session_duration_sec = Math.floor((record.session_duration_sec || 60) * 1.4);
482
+ record.month_end_anxiety = true;
483
+ } else {
484
+ record.month_end_anxiety = false;
485
+ }
486
+ }
487
+
488
+ if (record.event === "balance checked") {
489
+ if (dayOfMonth >= 28) {
490
+ record.account_balance = Math.floor((record.account_balance || 2500) * 0.7);
491
+ record.month_end_check = true;
492
+ } else {
493
+ record.month_end_check = false;
494
+ }
495
+ }
496
+ }
497
+
498
+ // ===============================================================
499
+ // Hook #3, #4, #5: EVERYTHING - Complex behavioral patterns
500
+ // These hooks operate on the full event stream per user for
501
+ // fraud injection, low-balance churn, and budget discipline.
502
+ // ===============================================================
503
+ if (type === "everything") {
504
+ const userEvents = record;
505
+ const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
506
+
507
+ // -----------------------------------------------------------
508
+ // Hook #3: FRAUD DETECTION PATTERN
509
+ // 3% of users experience a fraud event sequence: a burst of
510
+ // 3-5 rapid high-value transactions within 1 hour, followed by
511
+ // card locked, dispute filed, and support contacted events.
512
+ // All injected events are tagged with fraud_sequence: true.
513
+ // -----------------------------------------------------------
514
+ if (chance.bool({ likelihood: 3 })) {
515
+ // Find the midpoint of the user's timeline
516
+ if (userEvents.length >= 2) {
517
+ const midIdx = Math.floor(userEvents.length / 2);
518
+ const midEvent = userEvents[midIdx];
519
+ const midTime = dayjs(midEvent.time);
520
+ const distinctId = midEvent.user_id;
521
+
522
+ // Inject 3-5 rapid high-value transactions
523
+ const burstCount = chance.integer({ min: 3, max: 5 });
524
+ const fraudEvents = [];
525
+
526
+ for (let i = 0; i < burstCount; i++) {
527
+ fraudEvents.push({
528
+ event: "transaction completed",
529
+ time: midTime.add(i * 10, "minutes").toISOString(),
530
+ user_id: distinctId,
531
+ transaction_type: "purchase",
532
+ amount: chance.integer({ min: 500, max: 3000 }),
533
+ merchant_category: chance.pickone(["online", "retail"]),
534
+ payment_method: "credit",
535
+ fraud_sequence: true,
536
+ });
537
+ }
538
+
539
+ // Card locked after the burst
540
+ fraudEvents.push({
541
+ event: "card locked",
542
+ time: midTime.add(burstCount * 10 + 5, "minutes").toISOString(),
543
+ user_id: distinctId,
544
+ reason: "suspicious_activity",
545
+ fraud_sequence: true,
546
+ });
547
+
548
+ // Dispute filed shortly after
549
+ fraudEvents.push({
550
+ event: "dispute filed",
551
+ time: midTime.add(burstCount * 10 + 30, "minutes").toISOString(),
552
+ user_id: distinctId,
553
+ dispute_amount: chance.integer({ min: 500, max: 3000 }),
554
+ reason: "unauthorized",
555
+ fraud_sequence: true,
556
+ });
557
+
558
+ // Support contacted
559
+ fraudEvents.push({
560
+ event: "support contacted",
561
+ time: midTime.add(burstCount * 10 + 45, "minutes").toISOString(),
562
+ user_id: distinctId,
563
+ channel: "phone",
564
+ issue_type: "card",
565
+ resolved: true,
566
+ fraud_sequence: true,
567
+ });
568
+
569
+ // Splice all fraud events into the user's timeline
570
+ userEvents.splice(midIdx + 1, 0, ...fraudEvents);
571
+ }
572
+ }
573
+
574
+ // -----------------------------------------------------------
575
+ // Hook #4: LOW BALANCE CHURN
576
+ // Users who have 3+ balance checks showing < $500 are
577
+ // "struggling". After day 30, 50% of their events are removed
578
+ // and remaining events are tagged low_balance_churn: true.
579
+ // -----------------------------------------------------------
580
+ let lowBalanceChecks = 0;
581
+ userEvents.forEach((event) => {
582
+ if (event.event === "balance checked" && (event.account_balance || 0) < 3000) {
583
+ lowBalanceChecks++;
584
+ }
585
+ });
586
+
587
+ if (lowBalanceChecks >= 3) {
588
+ const day30 = DATASET_START.add(30, "days");
589
+ for (let i = userEvents.length - 1; i >= 0; i--) {
590
+ const evt = userEvents[i];
591
+ if (dayjs(evt.time).isAfter(day30)) {
592
+ if (chance.bool({ likelihood: 50 })) {
593
+ userEvents.splice(i, 1);
594
+ } else {
595
+ evt.low_balance_churn = true;
596
+ }
597
+ }
598
+ }
599
+ }
600
+
601
+ // -----------------------------------------------------------
602
+ // Hook #5: BUDGET USERS SAVE MORE
603
+ // Users who created any budget have 2x savings contributions,
604
+ // 1.5x investment amounts, and extra savings goal events
605
+ // spliced into their timeline. Tagged budget_discipline: true.
606
+ // -----------------------------------------------------------
607
+ let hasBudget = false;
608
+ userEvents.forEach((event) => {
609
+ if (event.event === "budget created") {
610
+ hasBudget = true;
611
+ }
612
+ });
613
+
614
+ if (hasBudget) {
615
+ userEvents.forEach((event, idx) => {
616
+ const eventTime = dayjs(event.time);
617
+
618
+ // Double savings contributions
619
+ if (event.event === "savings goal set") {
620
+ event.monthly_contribution = Math.floor((event.monthly_contribution || 200) * 2);
621
+ event.budget_discipline = true;
622
+ }
623
+
624
+ // 1.5x investment amounts
625
+ if (event.event === "investment made") {
626
+ event.amount = Math.floor((event.amount || 250) * 1.5);
627
+ event.budget_discipline = true;
628
+ }
629
+
630
+ // Splice extra savings goal events (budget-conscious users set more goals)
631
+ if (event.event === "budget created" && chance.bool({ likelihood: 50 })) {
632
+ const extraGoal = {
633
+ event: "savings goal set",
634
+ time: eventTime.add(chance.integer({ min: 1, max: 7 }), "days").toISOString(),
635
+ user_id: event.user_id,
636
+ goal_type: chance.pickone(["emergency", "vacation", "car", "home"]),
637
+ target_amount: chance.integer({ min: 1000, max: 20000 }),
638
+ monthly_contribution: chance.integer({ min: 100, max: 800 }),
639
+ budget_discipline: true,
640
+ };
641
+ userEvents.splice(idx + 1, 0, extraGoal);
642
+ }
643
+ });
644
+ }
645
+ }
646
+
647
+ return record;
648
+ }
649
+ };
650
+
651
+ export default config;
652
+
653
+ /**
654
+ * ===================================================================
655
+ * NEEDLE IN A HAYSTACK - NEXBANK NEOBANK ANALYTICS
656
+ * ===================================================================
657
+ *
658
+ * A Chime/Revolut-style neobank dungeon with 8 deliberately architected
659
+ * analytics insights hidden in the data. This dungeon is designed to
660
+ * showcase advanced fintech product analytics patterns and demonstrate
661
+ * how to find "needles" (meaningful insights) in "haystacks" (large
662
+ * banking datasets).
663
+ *
664
+ * ===================================================================
665
+ * DATASET OVERVIEW
666
+ * ===================================================================
667
+ *
668
+ * - 5,000 users over 100 days
669
+ * - 360,000 events across 18 event types
670
+ * - 3 funnels (onboarding, transfer flow, investment journey)
671
+ * - Group analytics (500 households)
672
+ * - Lookup table (500 merchant/transaction entries)
673
+ * - Account tiers (Basic, Plus, Premium)
674
+ *
675
+ * ===================================================================
676
+ * THE 8 ARCHITECTED HOOKS
677
+ * ===================================================================
678
+ *
679
+ * Each hook creates a specific, discoverable analytics insight that
680
+ * simulates real-world fintech product behavior patterns.
681
+ *
682
+ * -------------------------------------------------------------------
683
+ * 1. PERSONAL VS BUSINESS ACCOUNTS (user)
684
+ * -------------------------------------------------------------------
685
+ *
686
+ * PATTERN: 20% of users are business accounts with employee_count
687
+ * (5-500), annual_revenue ($100K-$10M), and industry. The other 80%
688
+ * are personal accounts with age_range and life_stage.
689
+ *
690
+ * HOW TO FIND IT:
691
+ * - Segment users by: account_segment = "business" vs "personal"
692
+ * - Compare: Transaction volumes, average amounts, transfer patterns
693
+ * - Analyze: Industry distribution among business accounts
694
+ *
695
+ * EXPECTED INSIGHT: Business accounts have fundamentally different
696
+ * usage patterns - higher transaction amounts, more wire transfers,
697
+ * and different bill payment profiles.
698
+ *
699
+ * REAL-WORLD ANALOGUE: Identifying and serving different customer
700
+ * segments (B2C vs B2B) with tailored features and pricing.
701
+ *
702
+ * -------------------------------------------------------------------
703
+ * 2. PAYDAY PATTERNS (event)
704
+ * -------------------------------------------------------------------
705
+ *
706
+ * PATTERN: On the 1st and 15th of each month, direct deposit
707
+ * transactions are 2x bigger and tagged with payday: true. On days
708
+ * 1-3 and 15-17, transfer amounts have a 40% chance of being 1.5x
709
+ * larger, tagged with post_payday_spending: true.
710
+ *
711
+ * HOW TO FIND IT:
712
+ * - Chart: transaction completed count and amount by day of month
713
+ * - Filter: transaction_type = "direct_deposit"
714
+ * - Compare: Average transfer amount on days 1-3/15-17 vs other days
715
+ * - Look for: payday: true and post_payday_spending: true tags
716
+ *
717
+ * EXPECTED INSIGHT: Clear biweekly spikes in deposit amounts and
718
+ * subsequent spending activity. The 2-3 days after payday show
719
+ * elevated transfer activity as users move money around.
720
+ *
721
+ * REAL-WORLD ANALOGUE: Payroll cycle effects on banking activity.
722
+ * Banks use this to time marketing, credit offers, and overdraft
723
+ * protection promotions.
724
+ *
725
+ * -------------------------------------------------------------------
726
+ * 3. FRAUD DETECTION PATTERN (everything)
727
+ * -------------------------------------------------------------------
728
+ *
729
+ * PATTERN: 3% of users experience a fraud event sequence at their
730
+ * timeline midpoint: 3-5 rapid high-value transactions ($500-$3,000)
731
+ * within 1 hour, followed by card locked (suspicious_activity),
732
+ * dispute filed (unauthorized), and support contacted (phone/card).
733
+ * All injected events tagged with fraud_sequence: true.
734
+ *
735
+ * HOW TO FIND IT:
736
+ * - Filter events: fraud_sequence = true
737
+ * - Analyze: Time between fraud transactions (< 10 min gaps)
738
+ * - Funnel: transaction completed -> card locked -> dispute filed -> support contacted
739
+ * - Segment: Users with any fraud_sequence event
740
+ *
741
+ * EXPECTED INSIGHT: ~150 users (3% of 5,000) show a distinctive burst
742
+ * pattern of rapid high-value purchases followed by account lockdown.
743
+ * Clear temporal clustering of fraud events.
744
+ *
745
+ * REAL-WORLD ANALOGUE: Fraud detection in banking. Unusual velocity
746
+ * and amount patterns trigger automated alerts and account freezes.
747
+ *
748
+ * -------------------------------------------------------------------
749
+ * 4. LOW BALANCE CHURN (everything)
750
+ * -------------------------------------------------------------------
751
+ *
752
+ * PATTERN: Users with 3+ balance checks showing < $500 are
753
+ * "struggling" users. After day 30, 50% of their events are removed
754
+ * (simulating reduced app usage) and surviving events are tagged
755
+ * with low_balance_churn: true.
756
+ *
757
+ * HOW TO FIND IT:
758
+ * - Segment: Users where count of (balance checked, account_balance < 500) >= 3
759
+ * - Compare: Event counts before day 30 vs after day 30
760
+ * - Filter: low_balance_churn = true
761
+ * - Retention analysis: Compare D30+ retention for low vs healthy balance users
762
+ *
763
+ * EXPECTED INSIGHT: Struggling users show a dramatic drop in activity
764
+ * after the first month. Their engagement halves while healthy-balance
765
+ * users maintain consistent usage.
766
+ *
767
+ * REAL-WORLD ANALOGUE: Financial stress-driven churn. Users who
768
+ * can't maintain balances disengage from their banking app, which
769
+ * predicts account closure.
770
+ *
771
+ * -------------------------------------------------------------------
772
+ * 5. BUDGET USERS SAVE MORE (everything)
773
+ * -------------------------------------------------------------------
774
+ *
775
+ * PATTERN: Users who create any budget have 2x monthly savings
776
+ * contributions, 1.5x investment amounts, and extra savings goal
777
+ * events spliced into their timeline. All affected events tagged
778
+ * with budget_discipline: true.
779
+ *
780
+ * HOW TO FIND IT:
781
+ * - Segment: Users who did "budget created" vs those who didn't
782
+ * - Compare: Average monthly_contribution on savings goal events
783
+ * - Compare: Average investment amount
784
+ * - Count: savings goal set events per user (budget users have more)
785
+ * - Filter: budget_discipline = true
786
+ *
787
+ * EXPECTED INSIGHT: Budget creators save 2x more and invest 1.5x more
788
+ * than non-budget users. They also set more savings goals, showing
789
+ * compound financial wellness behavior.
790
+ *
791
+ * REAL-WORLD ANALOGUE: Financial planning tools drive better outcomes.
792
+ * Users who engage with budgeting features are more financially active
793
+ * and retain longer - a key product-market fit signal.
794
+ *
795
+ * -------------------------------------------------------------------
796
+ * 6. AUTO-PAY LOYALTY (event)
797
+ * -------------------------------------------------------------------
798
+ *
799
+ * PATTERN: Bill paid events with auto_pay = false have a 30% chance
800
+ * of being dropped entirely (simulating missed payments). Auto-pay
801
+ * users never miss. Surviving manual payments are tagged with
802
+ * manual_payment: true.
803
+ *
804
+ * HOW TO FIND IT:
805
+ * - Segment: bill paid events by auto_pay = true vs false
806
+ * - Compare: Total bill paid count per user in each segment
807
+ * - Calculate: Effective bill completion rate by segment
808
+ * - Filter: manual_payment = true for surviving manual payments
809
+ *
810
+ * EXPECTED INSIGHT: Auto-pay users have 100% bill completion while
811
+ * manual payers show only ~70% completion. This creates a clear
812
+ * reliability gap that would drive auto-pay adoption campaigns.
813
+ *
814
+ * REAL-WORLD ANALOGUE: Auto-pay enrollment is one of the strongest
815
+ * retention predictors in fintech. Users who set up auto-pay are
816
+ * less likely to miss payments and less likely to churn.
817
+ *
818
+ * -------------------------------------------------------------------
819
+ * 7. PREMIUM TIER VALUE (event)
820
+ * -------------------------------------------------------------------
821
+ *
822
+ * PATTERN: Premium tier users get 3x reward values and 2x investment
823
+ * sell returns. Plus tier users get 1.5x rewards. Tagged with
824
+ * premium_reward: true and premium_returns: true respectively.
825
+ *
826
+ * HOW TO FIND IT:
827
+ * - Segment: Events by account_tier (basic, plus, premium)
828
+ * - Compare: Average reward value on reward redeemed events
829
+ * - Compare: Average amount on investment made (action = sell)
830
+ * - Filter: premium_reward = true, premium_returns = true
831
+ * - Analyze: Total reward value per user by tier
832
+ *
833
+ * EXPECTED INSIGHT: Clear tier-based value curve. Premium users
834
+ * earn 3x the rewards and 2x the investment returns of Basic users,
835
+ * with Plus users in between. This validates tier pricing.
836
+ *
837
+ * REAL-WORLD ANALOGUE: Premium banking tiers that provide tangible
838
+ * financial benefits. The reward multiplier justifies the monthly
839
+ * fee and drives upgrade conversions.
840
+ *
841
+ * -------------------------------------------------------------------
842
+ * 8. MONTH-END ANXIETY (event)
843
+ * -------------------------------------------------------------------
844
+ *
845
+ * PATTERN: On the last 3 days of each month (day >= 28), app sessions
846
+ * are 40% longer and balance checks show 30% lower balances. Tagged
847
+ * with month_end_anxiety: true and month_end_check: true.
848
+ *
849
+ * HOW TO FIND IT:
850
+ * - Chart: Average session_duration_sec by day of month
851
+ * - Chart: Average account_balance on balance checked by day of month
852
+ * - Filter: month_end_anxiety = true, month_end_check = true
853
+ * - Compare: Day 1-27 vs day 28-31 engagement metrics
854
+ *
855
+ * EXPECTED INSIGHT: Users spend 40% more time in the app at month-end,
856
+ * checking lower balances. This reflects pre-bill-pay anxiety and
857
+ * end-of-month financial stress.
858
+ *
859
+ * REAL-WORLD ANALOGUE: Month-end financial anxiety drives app
860
+ * engagement spikes. Banks can leverage this pattern for timely
861
+ * overdraft protection offers, savings nudges, and bill reminders.
862
+ *
863
+ * ===================================================================
864
+ * ADVANCED ANALYSIS IDEAS
865
+ * ===================================================================
866
+ *
867
+ * CROSS-HOOK PATTERNS:
868
+ *
869
+ * 1. Budget + Low Balance: Do budget creators (Hook #5) avoid the
870
+ * low-balance churn pattern (Hook #4)? Budget discipline should
871
+ * correlate with healthier balances.
872
+ *
873
+ * 2. Premium + Auto-Pay: Do premium tier users (Hook #7) have higher
874
+ * auto-pay adoption than basic users? Does tier upgrading predict
875
+ * auto-pay enrollment?
876
+ *
877
+ * 3. Fraud + Churn: Do fraud victims (Hook #3) churn more than
878
+ * non-victims? Does support resolution quality affect post-fraud
879
+ * retention?
880
+ *
881
+ * 4. Payday + Month-End: Compare payday spending spikes (Hook #2)
882
+ * with month-end anxiety (Hook #8). Do payday spenders run out
883
+ * of money by month-end?
884
+ *
885
+ * 5. Business vs Personal Fraud: Are business accounts (Hook #1)
886
+ * more or less likely to be fraud targets (Hook #3)?
887
+ *
888
+ * COHORT ANALYSIS:
889
+ *
890
+ * - Cohort by account_tier: Track upgrade paths and value realization
891
+ * across Basic -> Plus -> Premium
892
+ * - Cohort by signup_channel: Do referral users have better retention
893
+ * and higher balances?
894
+ * - Cohort by income_bracket: How does income correlate with feature
895
+ * adoption (budgets, investments, savings goals)?
896
+ * - Cohort by credit_score_range: Do higher credit scores predict
897
+ * loan approvals and premium tier adoption?
898
+ *
899
+ * FUNNEL ANALYSIS:
900
+ *
901
+ * - Onboarding Funnel: account opened -> app session -> balance checked
902
+ * by account type and signup channel
903
+ * - Transfer Flow: app session -> transfer sent -> notification opened
904
+ * by tier and platform
905
+ * - Investment Journey: balance checked -> investment made -> reward redeemed
906
+ * by income bracket and budget creation status
907
+ *
908
+ * ===================================================================
909
+ * EXPECTED METRICS SUMMARY
910
+ * ===================================================================
911
+ *
912
+ * Hook | Metric | Baseline | Hook Effect | Ratio
913
+ * ----------------------|-----------------------|----------|-------------|------
914
+ * Personal vs Business | Avg transaction amt | $50 | $200+ | ~4x
915
+ * Payday Patterns | Deposit amount | $50 | $100 | 2x
916
+ * Fraud Detection | Users affected | 0% | 3% | --
917
+ * Low Balance Churn | D30+ event count | 100% | 50% | 0.5x
918
+ * Budget Discipline | Monthly contribution | $200 | $400 | 2x
919
+ * Auto-Pay Loyalty | Bill completion rate | 100% | 70% | 0.7x
920
+ * Premium Tier Value | Reward value | $10 | $30 | 3x
921
+ * Month-End Anxiety | Session duration | 60s | 84s | 1.4x
922
+ *
923
+ * ===================================================================
924
+ * HOW TO RUN THIS DUNGEON
925
+ * ===================================================================
926
+ *
927
+ * From the dm4 root directory:
928
+ *
929
+ * npm start
930
+ *
931
+ * Or programmatically:
932
+ *
933
+ * import generate from './index.js';
934
+ * import config from './dungeons/harness-fintech.js';
935
+ * const results = await generate(config);
936
+ *
937
+ * OUTPUT FILES (with writeToDisk: false):
938
+ *
939
+ * - needle-haystack-fintech__events.json.gz - All event data
940
+ * - needle-haystack-fintech__user_profiles.json.gz - User profiles
941
+ * - needle-haystack-fintech__group_profiles.json.gz - Household profiles
942
+ * - needle-haystack-fintech__transaction_id_lookup.json.gz - Merchant catalog
943
+ *
944
+ * ===================================================================
945
+ * TESTING YOUR ANALYTICS PLATFORM
946
+ * ===================================================================
947
+ *
948
+ * This dungeon is perfect for testing:
949
+ *
950
+ * 1. Segmentation: Can you separate business vs personal accounts?
951
+ * 2. Time Patterns: Can you detect the biweekly payday cycle?
952
+ * 3. Anomaly Detection: Can you find the fraud burst patterns?
953
+ * 4. Churn Prediction: Can you predict churn from low balance signals?
954
+ * 5. Feature Impact: Can you measure budget tools' effect on savings?
955
+ * 6. Behavioral Analysis: Can you quantify auto-pay vs manual reliability?
956
+ * 7. Tier Analysis: Can you calculate reward ROI by account tier?
957
+ * 8. Temporal Patterns: Can you identify month-end anxiety in the data?
958
+ *
959
+ * ===================================================================
960
+ * WHY "NEEDLE IN A HAYSTACK"?
961
+ * ===================================================================
962
+ *
963
+ * Each hook is a "needle" - a meaningful, actionable insight hidden in a
964
+ * "haystack" of 360K events. The challenge is:
965
+ *
966
+ * 1. FINDING the needles (discovery)
967
+ * 2. VALIDATING they're real patterns (statistical significance)
968
+ * 3. UNDERSTANDING why they matter (business impact)
969
+ * 4. ACTING on them (product decisions)
970
+ *
971
+ * This mirrors real-world fintech analytics: your transaction data contains
972
+ * valuable insights about user behavior, but you need the right tools and
973
+ * skills to find them.
974
+ *
975
+ * ===================================================================
976
+ */